This is an R Markdown Notebook. When you execute code within the notebook, the results appear beneath the code.

Introduction

The objective of this notebook is to discretize HRUs in a watershed and create a weighting matrix that describes the hillslope-stream network connectivity in a catchment. This notebook is meant to replace the spatial discretization workflow (DynatopSpatialFunctionExplicitReaches) for the modified dynamic TOPMODEL described by Mahoney et al. (2022) J. Hydrol.

To run this notebook, it is necessary to first install kernels for Python and R and install arcpy to the environment from which the notebook is run (see instructions here). Instances when switching the kernel is necessary will be denoted in a Markdown cell.

Step 1: Load in necessary libraries for both R and Python

We will be using both R and python in this example. Python will be called via the reticulate package. We will be using the python installation from arcgis pro. A major advantage of using the R Notebook document over, for example, Jupyter Notebook is the ability to store variables generated by both python and R in the environment. Thus we can use variables generated in R with the arcgis API, have the user-friendliness of Rstudio, and have the replicability of a Notebook document.

setwd('C:/Users/david/OneDrive/Desktop/Mahoney Research/2-Mahoney-EPA/modeling-streamflow-permanence/')

# Load in libraries
library(reticulate)
library(sp)
library(raster)
library(dynatopmodel)
library(topmodel)
library(ecbtools)
library(cluster)
library(factoextra)
library(tidyverse)
library(NbClust)
library(basicClEval)
library(ggplot2)
library(mapview)
library(doParallel)
library(foreach)
use_python('C:/Program Files/ArcGIS/Pro/bin/Python/envs/arcgispro-py3/python.exe',required=T)
Warning in Sys.setlocale("LC_CTYPE", ctype) :
  using locale code page other than 65001 ("UTF-8") may cause problems
arcpy <- import("arcpy")
arcgis <- import("arcgis")
py_math <- import("math")
py_numpy <- import("numpy")
ipython <- import("IPython")

Step 2: Import Rasters and calculate TWI

Note: This code will calculate the TWI using the D8 flow direction. It is possible to import a DINF or MFD raster here as well and use this to run the analysis, assuming that it is already in the GDB.

# Read in the rasters that we'd need for HRU creation
dem <- raster('SpatialInputData/fr1meterDEM.tif')
soils <- raster('SpatialInputData/FRSoils1mMask.tif')
TWI.dinf <- raster('C:/Users/david/OneDrive/Desktop/Mahoney Research/2-Mahoney-EPA/modeling-streamflow-permanence/SpatialInputData/fr_twi_dinf.tif')
TWI.dinf[TWI.dinf==0] <- NA # We do this because the areas outside of the raster have a value 0 and we want these to be set to NA

Step 3: Create a HRU raster based on the TWI, soils, and other data using R

Now, we will switch over to R and create an HRU map of for the watershed. Make sure to switch the kernel from python (esri) to R

Step 3.1: Install libraries and read in raster files

Relevant libraries include spatial and raster libraries, cluster analyses, and tidyverse. Note, we are creating HRUs with the following rasters:

  • DEM

  • Soils

  • TWI

Other relevant layers that could be used include:

  • LULC

  • Ksat

  • Porosity

  • Aspect

  • Curvature

  • Temperature

  • Precipitation

Note: there is some degree of customizability here. Any relevant layer could be added.

Step 3.3: Create an elbow plot showing the number of HRUs which should be created

The total within sum of squares for each cluster is used to estimate the variability for each iteration.

Note: this code takes a decent amount of time to run.

wss.raster <- function(k) {
    # Run the K-means analysis
    kmeans.raster <- raster.kmeans(raster.stack, k, iter.max=10, nstart = 10, geo = T, geo.weight = 1)
    
    # Load in the data and get into vector form
    HRU.vector <- data.frame('hru' = as.vector(kmeans.raster))
    TWI.vector <- data.frame('TWI' = as.vector(TWI.dinf.cut))
    soils.vector <- data.frame('soils' = as.vector(soils))
    elev.bands.vector <- data.frame('Elev.bands' = as.vector(elev.bands))
    
    # Put the raster vectors into a cleaned data frame
    rasters.df <- data.frame(HRU.vector,TWI.vector,soils.vector,elev.bands.vector)
    rasters.df.clean <- rasters.df[complete.cases(rasters.df),]
  
    # Calculate the within cluster sum of squares
    within.ss <- wcss(rasters.df.clean[,-1],rasters.df.clean[,1])
    
    # Return the total within cluster sum of squares
    return(within.ss$WCSS)
}

set.seed(123)

# Compute and plot within sum squares for k = 1 to k = 40
k.values <- 1:30

# extract within sum of squares for 1-40 clusters
wss_values <- map_dbl(k.values,wss.raster)
Warning: Quick-TRANSfer stage steps exceeded maximum (= 20786250)
Warning: Quick-TRANSfer stage steps exceeded maximum (= 20786250)
# plot within sum of squares vs cluster (HRU) number
plot(k.values,wss_values,type='b',pch=19,frame=F,xlab='Number of clusters',ylab='Total within-cluster sum of sq')

Step 3.4: Plot the clusters for reference

We will look at the various clusters created and how they appear within relation to one another

# Load in the data and get into vector form
HRU.vector <- data.frame('hru' = as.vector(HRU))
TWI.vector <- data.frame('TWI' = as.vector(TWI.dinf.cut))
TWI.nc.vector <- data.frame('TWI.nc'=as.vector(TWI.dinf))
soils.vector <- data.frame('soils' = as.vector(soils))
elev.bands.vector <- data.frame('Elev.bands' = as.vector(elev.bands))
elev.bands.nc.vector <- data.frame('Elev.bands.nc' = as.vector(dem))

# Put the raster vectors into a cleaned data frame
rasters.df <- data.frame(HRU.vector,TWI.vector,TWI.nc.vector,soils.vector,elev.bands.vector,elev.bands.nc.vector)
rasters.df.clean <- rasters.df[complete.cases(rasters.df),]

# Plot the elev bands vs twi with HRU as color
ggplot(data=rasters.df.clean, aes(x=TWI.nc,y=Elev.bands.nc,col=hru))+geom_point()


# Note - the straight lines are due to the cuts that we creted. We could just as easily not include these cuts and other geometries would come through

Step 3.5: Sort the HRUs by TWI and add in the stream raster

In this step, we will first sort HRUs by TWI value (high TWI = high number HRU), then we will “burn in” a stream network that has been previously delineated. This represents the geomorphic stream channel, to which the watershed uplands will contribute water.

The raster need to have the following characteristics:

  • The resolution, extent, and snapping should be the same as the DEM

  • The raster is derived from a Flow accumulation raster with an upstream contributing area threshold applied

  • The threshold should create a stream network that is similar to the stream mapped in the field

  • The reaches should be created using the stream link/order tools in ArcGIS

  • The shapefile created from these stream tools should be buffered such that a contiguous stream network raster can be created

  • Here, a buffer of 2 cell widths was used (~1.524 * 2 m)

## Calculate the mean TWI for each HRU - relabel the HRUs such that high valued TWI is the highest number HRU and low valued TWI is the lowest HRU
## Note - this isn't necessary for dynatopmodel, but may be necessary for dynatop
mean.twi <- data.frame(matrix(nrow=length(unique(HRU)),ncol=5))
names(mean.twi) <- c('HRU.name','atb.bar.unsort','id.new','atb.bar','id')
mean.twi$HRU.name <- unique(HRU)

hillslope.hru.matrix <- as.matrix(HRU)
TWI.dinf.matrix <- as.matrix(TWI.dinf)

mean.twi$atb.bar.unsort <- zonal(x=TWI.dinf,z=HRU,fun='mean')[,2]
mean.twi$atb.bar <- sort(mean.twi$atb.bar.unsort,decreasing=F)
mean.twi$id.new <- match(mean.twi$atb.bar.unsort,mean.twi$atb.bar)
mean.twi$id <- 1:length(unique(HRU))

reclassify.matrix <- data.frame(matrix(nrow=length(unique(HRU)),ncol=2))
names(reclassify.matrix) <- c('is','becomes')
reclassify.matrix$is <- mean.twi$HRU.name
reclassify.matrix$becomes <- mean.twi$id.new

HRU <- reclassify(HRU,reclassify.matrix)

## Overlay the stream network atop the Hru raster
stream <- raster('SpatialInputData/FR_stream_network.tif')
stream[stream==0] <- NA
HRU <- HRU*10+max(max(unique(stream)),100)               # Uniquely identifying the upland HRUs
HRU[stream>0] <- stream[stream>0]                     # Identify the cells from the HRU raster that overlap with the stream, set those equal to the reach number 
writeRaster(HRU,"SpatialInputData/FR_HRU.tif",overwrite=T)

image(HRU)


twi.zonal <- zonal(x=TWI.dinf,z=HRU,fun='mean')
TWI.zonal <- reclassify(HRU,twi.zonal)

image(TWI.zonal)


stream.lump <- stream
stream.lump[stream >= 1] <- 1
HRU.lump <- HRU
HRU.lump[stream.lump>0] <- stream.lump[stream.lump>0]

writeRaster(HRU.lump,"SpatialInputData/FR_HRU_lump.tif",overwrite=T)
writeRaster(stream.lump,"SpatialInputData/FR_stream_lump.tif",overwrite=T)

image(HRU.lump)

Step 3.6 Visualize the HRU/Stream Raster

Note - I’m going to do this with the mapview function in R, but this could be done using the arcpy and arcgis modules in python. The reason for not using python is because if we interrupt the kernel we’ll have to reload variables from above

mapView(HRU, maxpixels =  2102000) + mapView(TWI.zonal, maxpixels =  2102000) + 
  mapView(HRU.lump,maxpixels =  2102000) + mapView(stream.lump, maxpixels =  2102000) +
  mapView(stream,maxpixels =  2102000)
Warning in showSRID(uprojargs, format = "PROJ", multiline = "NO", prefer_proj = prefer_proj) :
  Discarded ellps WGS 84 in Proj4 definition: +proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 +y_0=0 +k=1 +units=m +nadgrids=@null +wktext +no_defs +type=crs
Warning in showSRID(uprojargs, format = "PROJ", multiline = "NO", prefer_proj = prefer_proj) :
  Discarded datum World Geodetic System 1984 in Proj4 definition

Step 4: Create a flow weighting raster

In this step, we will create the weighting matrix to run the dynatopmodel analysis. We do this twice. Once for the “Explicit matrix” which shows the connectivity of upland cells that contribute to a specific reach. A second time for the “Lumped Matrix” which shows the connectivity of cells to a “lumped” stream network. As of 8/2/2022, the “dynatopmodel” package is not compatible with explicit reaches.

Step 4.1: Calculate the explicit downstream matrix

This process returns the value of the HRU immediately downstream of a cell. This algorithm strictly works for a D8 flow direction, but could be expanded for DInf at a later time.

Make sure to switch over to the R kernel

# First, create the explicit matrix
library(sp)
library(raster)

HRU <- raster('SpatialInputData/FR_HRU.tif')
fdr <- raster('SpatialInputData/fr_fdr')


HRU.matrix <- as.matrix(HRU)
fdr.matrix <- as.matrix(fdr)

fdr.downstream.matrix <- matrix(nrow=nrow(HRU.matrix),ncol=ncol(HRU.matrix))

for (i in 1:nrow(HRU.matrix)) {
  for (j in 1:ncol(HRU.matrix)) {
    if (is.na(fdr.matrix[i,j])) {
      fdr.downstream.matrix[i,j] <- NA
    } else {
      if (fdr.matrix[i,j]==1) {
        fdr.downstream.matrix[i,j] <- HRU.matrix[i,j+1]
      } else if (fdr.matrix[i,j]==2) {
        fdr.downstream.matrix[i,j] <- HRU.matrix[i+1,j+1]
      } else if (fdr.matrix[i,j]==4) {
        fdr.downstream.matrix[i,j] <- HRU.matrix[i+1,j]
      } else if (fdr.matrix[i,j]==8) {
        fdr.downstream.matrix[i,j] <- HRU.matrix[i+1,j-1]
      } else if (fdr.matrix[i,j]==16) {
        fdr.downstream.matrix[i,j] <- HRU.matrix[i,j-1]
      } else if (fdr.matrix[i,j]==32) {
        fdr.downstream.matrix[i,j] <- HRU.matrix[i-1,j-1]
      } else if (fdr.matrix[i,j]==64) {
        fdr.downstream.matrix[i,j] <- HRU.matrix[i-1,j]
      } else if (fdr.matrix[i,j]==128) {
        fdr.downstream.matrix[i,j] <- HRU.matrix[i-1,j+1]
      } else {
        fdr.downstream.matrix[i,j] <- NA
      }
    }
  }
}

Step 4.2: Calculate the explicit weighting matrix which shows the number of cells from one HRU to contribute to downstream HRUs.

In this step, we will create a weighting matrix that outputs the proportion of flow generated in one cell that will be redistributed to downstream cells.

This works by:

  • identifying all cells that belong to a certain HRU

  • identifying all cells which belong to a downstream HRU

  • identifying the cells where both of these conditions are true

  • returning the number of cells where both are true

In this regard, the output will be all cells which belong to HRU i that flow into HRU j.

Note: I rewrote some of this code. The run time goes from about 50 minutes to approximately 50 seconds.

print('Running weighting matrix...')
[1] "Running weighting matrix..."
no.HRU.explicit <- length(unique(HRU))
HRU.explicit.list <- unique(HRU)

downstream.weighting.matrix <- matrix(nrow=no.HRU.explicit,ncol=no.HRU.explicit)
start.time <- Sys.time()

for (i in 1:no.HRU.explicit) {
  print(HRU.explicit.list[i])
  for (j in 1:no.HRU.explicit) {
    downstream.weighting.matrix[i,j] <- sum((HRU.matrix==HRU.explicit.list[i])&(fdr.downstream.matrix==HRU.explicit.list[j]),na.rm=T)
  }
}
[1] 1
[1] 2
[1] 3
[1] 4
[1] 5
[1] 6
[1] 7
[1] 8
[1] 9
[1] 10
[1] 11
[1] 12
[1] 13
[1] 14
[1] 15
[1] 16
[1] 17
[1] 18
[1] 19
[1] 20
[1] 21
[1] 22
[1] 23
[1] 24
[1] 25
[1] 26
[1] 27
[1] 28
[1] 29
[1] 30
[1] 31
[1] 32
[1] 33
[1] 110
[1] 120
[1] 130
[1] 140
[1] 150
[1] 160
[1] 170
[1] 180
[1] 190
[1] 200
[1] 210
[1] 220
[1] 230
[1] 240
[1] 250
[1] 260
[1] 270
[1] 280
[1] 290
[1] 300
end.time <- Sys.time()
end.time-start.time
Time difference of 53.847 secs
options("scipen"=100, "digits"=5)

rownames(downstream.weighting.matrix) <- unique(HRU)
colnames(downstream.weighting.matrix) <- unique(HRU)

weighting.matrix <- downstream.weighting.matrix/rowSums(downstream.weighting.matrix)

rownames(weighting.matrix) <- unique(HRU)
colnames(weighting.matrix) <- unique(HRU)

rowSums(weighting.matrix)
  1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20 
  1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1 
 21  22  23  24  25  26  27  28  29  30  31  32  33 110 120 130 140 150 160 170 
  1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1 
180 190 200 210 220 230 240 250 260 270 280 290 300 
  1   1   1   1   1   1   1   1   1   1   1   1   1 

Step 4.3: Initialize the lumped downstream matrix

Note that the current implementation of dynatopmodel does not support the explicit streams, as far as I am aware. Dynatop may, but I haven’t investigated the model in depth as of 8/3/2022

# Next, create the lumped matrix

HRU.lump <- raster('SpatialInputData/FR_HRU_lump.tif')
fdr <- raster('SpatialInputData/fr_fdr')

HRU.lump.matrix <- as.matrix(HRU.lump)
fdr.matrix <- as.matrix(fdr)

fdr.downstream.lump.matrix <- matrix(nrow=nrow(HRU.lump.matrix),ncol=ncol(HRU.lump.matrix))

for (i in 1:nrow(HRU.lump.matrix)) {
  for (j in 1:ncol(HRU.lump.matrix)) {
    if (is.na(fdr.matrix[i,j])) {
      fdr.downstream.lump.matrix[i,j] <- NA
    } else {
      if (fdr.matrix[i,j]==1) {
        fdr.downstream.lump.matrix[i,j] <- HRU.lump.matrix[i,j+1]
      } else if (fdr.matrix[i,j]==2) {
        fdr.downstream.lump.matrix[i,j] <- HRU.lump.matrix[i+1,j+1]
      } else if (fdr.matrix[i,j]==4) {
        fdr.downstream.lump.matrix[i,j] <- HRU.lump.matrix[i+1,j]
      } else if (fdr.matrix[i,j]==8) {
        fdr.downstream.lump.matrix[i,j] <- HRU.lump.matrix[i+1,j-1]
      } else if (fdr.matrix[i,j]==16) {
        fdr.downstream.lump.matrix[i,j] <- HRU.lump.matrix[i,j-1]
      } else if (fdr.matrix[i,j]==32) {
        fdr.downstream.lump.matrix[i,j] <- HRU.lump.matrix[i-1,j-1]
      } else if (fdr.matrix[i,j]==64) {
        fdr.downstream.lump.matrix[i,j] <- HRU.lump.matrix[i-1,j]
      } else if (fdr.matrix[i,j]==128) {
        fdr.downstream.lump.matrix[i,j] <- HRU.lump.matrix[i-1,j+1]
      } else {
        fdr.downstream.lump.matrix[i,j] <- NA
      }
    }
  }
}

Step 4.4: Calculate the lumped weighting matrix which shows the number of cells from one HRU to contribute to downstream HRUs.

Note: I have rewritten the code here. The previous run time for this chunk was 10 minutes. Now it is about 10 seconds.

print('Running weighting matrix...')
[1] "Running weighting matrix..."
no.HRU.lump <- length(unique(HRU.lump))
list.HRU.lump <- unique(HRU.lump)

downstream.weighting.matrix.lump <- matrix(nrow=length(unique(HRU.lump)),ncol=length(unique(HRU.lump)))
start.time <- Sys.time()
for (i in 1:no.HRU.lump) {
  print(list.HRU.lump[i])
  for (j in 1:no.HRU.lump) {
    downstream.weighting.matrix.lump[i,j] <- sum((HRU.lump.matrix==list.HRU.lump[i])&(fdr.downstream.lump.matrix==list.HRU.lump[j]),
                                                 na.rm=T)
  }
}
[1] 1
[1] 110
[1] 120
[1] 130
[1] 140
[1] 150
[1] 160
[1] 170
[1] 180
[1] 190
[1] 200
[1] 210
[1] 220
[1] 230
[1] 240
[1] 250
[1] 260
[1] 270
[1] 280
[1] 290
[1] 300
end.time <- Sys.time()
end.time-start.time
Time difference of 10.993 secs
options("scipen"=100, "digits"=5)

rownames(downstream.weighting.matrix.lump) <- unique(HRU.lump)
colnames(downstream.weighting.matrix.lump) <- unique(HRU.lump)

weighting.matrix.lump <- downstream.weighting.matrix.lump/rowSums(downstream.weighting.matrix.lump)

rownames(weighting.matrix.lump) <- unique(HRU.lump)
colnames(weighting.matrix.lump) <- unique(HRU.lump)

rowSums(weighting.matrix.lump)
  1 110 120 130 140 150 160 170 180 190 200 210 220 230 240 250 260 270 280 290 
  1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1 
300 
  1 

Step 5: Create the Groups Matrix

Next, we will create a “Groups” matrix which will specify the initial parameters for dynamic TOPMODEL.

Note - I did notice some differences between atb.bar from the 20 HRUs discretized here and the 2- HRUs discretized with dynatopmodel. It is related to how the HRUs are discretized. There is more variability with the dynatopmodel approach.

Items we will need to include in the table:

  • id - the ID of the channel, typically channels are ranked 0-100 and HRUs are ranked based on TWI with ID > 100

  • tag - same as id

  • chan.no - the channel number (note - only for IDs classified as channels)

  • area_pc - the percent area that the HRU takes up

  • area - the area m^2 that the HRU takes up

  • sbar - the average slope of the HRU (m/m)

  • atb.bar - the average TWI for the HRU

  • gauge.id - the rain gauge contributing to the HRU

  • catch.id - the catchment in which the HRU is situated

  • srz_max - maximum depth of root zone parameter value initialized as 0.1

  • ln_t0 - ln t0 parameter value initialized as 7

  • m - exponential decline of transmissivity parameter initialized as 0.01

  • srz0 - initial root zone storage parameter initialized as 0

  • td - td parameter initialized as 1

  • vchan - v chan parameter (for channels only) parameterized as 1000

  • vof - overland v parameter (for HRUs only) parameterized as 100

  • k0 - k0 parameter initialized as 1e+8

  • CD - CD parameter (unused) initialized as 0.1

  • sd_max - sd_max parameter initialized as 0.5

  • pe_fact - potential evapotranspiration factor used to scale PET, initialized as 1

  • vof_fact - vof factor initialized as 1

  • rain_fact - rain factor used to scale precip amount, initialized as 1

  • mann.n - mannings n initialized as 0.01

  • S0 - initial slope, parameterized as 0.1

  • ex_max - ex max parameterized as 1

Step 5.1 Create the groups matrix for the explicit reaches

# Enter the cell size 
cell.size <- as.numeric(readline('Enter the resolution of the raster in m (for 10-m raster enter 10): '))
1.524

# Create functions to determine the area of the raster
area_cell <- function(raster.matrix,raster_ID) {
  area_c <- sum(raster.matrix==raster_ID, na.rm=T)
  return(area_c)
}

# Create function to determine the zonal mean (unused)
mean_raster <- function(raster.matrix, HRU.matrix, raster_ID) {
  mean_rast <- mean(raster.matrix[HRU.matrix==raster_ID],na.rm=T)
  return(mean_rast)
}

# Calculate the slope 
slope <- terrain(dem,opt='slope',units='tangent')

# Initial ize the groups 
groups.explicit <- data.frame(matrix(nrow=length(unique(HRU)),ncol=26))
names(groups.explicit) <- c('id','tag','chan.no','order','area_pc','area','sbar','atb.bar','gauge.id','catch.id','srz_max','ln_t0','m','srz0','td','vchan','vof','k0','CD','sd_max','pe_fact','vof_fact','rain_fact','mann.n','S0','ex_max')

# Assign the groups
groups.explicit$id <- unique(HRU)
groups.explicit$tag <- unique(HRU)
groups.explicit$chan.no[1:length(unique(stream))] <- unique(stream)
groups.explicit$order <- 1:length(unique(HRU))
groups.explicit$area_pc <- sapply(X=groups.explicit$id,FUN=area_cell,raster.matrix=HRU.matrix)/sum(!is.na(HRU.matrix))*100
groups.explicit$area <- groups.explicit$area_pc*sum(!is.na(HRU.matrix))*cell.size^2/100
groups.explicit$sbar <- zonal(x=slope,z=HRU,fun='mean')[,2]
groups.explicit$atb.bar[(1+length(unique(stream))):length(unique(HRU))] <- zonal(x=TWI.dinf,z=HRU,fun='mean')[(1+length(unique(stream))):length(unique(HRU)),2]
groups.explicit$gauge.id <- 1
groups.explicit$catch.id <- 1
groups.explicit$srz_max <- 0.1
groups.explicit$ln_t0 <- 7
groups.explicit$m <- 0.01
groups.explicit$srz0 <- 0
groups.explicit$td <- 1
groups.explicit$vchan[1:length(unique(stream))] <- 1000
groups.explicit$vof[(1+length(unique(stream))):length(unique(HRU))] <- 100
groups.explicit$k0 <- 1e+8
groups.explicit$CD <- 0.1
groups.explicit$sd_max <- 0.5
groups.explicit$pe_fact <- 1
groups.explicit$vof_fact <- 1
groups.explicit$rain_fact <- 1
groups.explicit$mann.n <- 0.01
groups.explicit$S0 <- 0.1
groups.explicit$ex_max <- 1

Step 5.2: Create the groups matrix for the lumped reaches


# Calculate the slope 
slope <- terrain(dem,opt='slope',units='tangent')

# Initialize the groups 
groups.lump <- data.frame(matrix(nrow=no.HRU.lump,ncol=26))
names(groups.lump) <- c('id','tag','chan.no','order','area_pc','area','sbar','atb.bar','gauge.id','catch.id','srz_max','ln_t0','m','srz0','td','vchan','vof','k0','CD','sd_max','pe_fact','vof_fact','rain_fact','mann.n','S0','ex_max')

# Assign the groups
groups.lump$id <- list.HRU.lump
groups.lump$tag <- list.HRU.lump
groups.lump$chan.no[1:length(unique(stream.lump))] <- unique(stream.lump)
groups.lump$order <- 1:no.HRU.lump
groups.lump$area_pc <- sapply(X=groups.lump$id,
                         FUN=area_cell,raster.matrix=HRU.lump.matrix)/sum(!is.na(HRU.lump.matrix))*100
groups.lump$area <- groups.lump$area_pc*sum(!is.na(HRU.lump.matrix))*cell.size^2/100
groups.lump$sbar <- zonal(x=slope,z=HRU.lump,fun='mean')[,2]
groups.lump$atb.bar[(1+length(unique(stream.lump))):length(unique(HRU.lump))] <- zonal(x=TWI.dinf,z=HRU.lump,fun='mean')[(1+length(unique(stream.lump))):length(unique(HRU.lump)),2]
groups.lump$gauge.id <- 1
groups.lump$catch.id <- 1
groups.lump$srz_max <- 0.1
groups.lump$ln_t0 <- 7
groups.lump$m <- 0.01
groups.lump$srz0 <- 0
groups.lump$td <- 1
groups.lump$vchan[1:length(unique(stream.lump))] <- 1000
groups.lump$vof[(1+length(unique(stream.lump))):length(unique(HRU.lump))] <- 100
groups.lump$k0 <- 1e+8
groups.lump$CD <- 0.1
groups.lump$sd_max <- 0.5
groups.lump$pe_fact <- 1
groups.lump$vof_fact <- 1
groups.lump$rain_fact <- 1
groups.lump$mann.n <- 0.01
groups.lump$S0 <- 0.1
groups.lump$ex_max <- 1

Step 6: Routing Matrix

In this step, we will create routing matrix that bins lists the length of each cell to the outlet and the frequency.

# Determine the flow length from each cell in the stream to the catchment outlet
outFlowLength <- arcpy$sa$FlowLength('SpatialInputData/fr_fdr',"DOWNSTREAM")
# Note - the next line only needs to be saved once. 
#outFlowLength$save(paste0(getwd(),'/SpatialInputData/Flow_length.tif'))

Flow.length <- raster('SpatialInputData/Flow_length.tif')
Flow.length.stream <- stream.lump
Flow.length.stream[!is.na(stream.lump)] <- Flow.length[stream.lump==1]
writeRaster(Flow.length.stream,'SpatialInputData/Flow_length_stream.tif', overwrite =T)

input.units <- readline('Was the original DEM in units of ft or m (enter ft or m): ')
ft

breaks.hist <- 10      # User defined number of breaks

RoutingTable <- data.frame(matrix(nrow=breaks.hist,ncol=2))
names(RoutingTable) <- c('flow.len', 'prop')

if(input.units == 'ft') {
  Flow.length.stream.fix <- Flow.length.stream * 0.3048
  Flow.length.vector <- na.omit(as.numeric(as.vector(Flow.length.stream.fix)))
  
  RoutingHist <- hist(Flow.length.vector,breaks=seq(min(Flow.length.vector),max(Flow.length.vector),length.out=breaks.hist+1))
  RoutingTable$flow.len <- RoutingHist$breaks[-1]
  RoutingTable$prop <- RoutingHist$counts/sum(RoutingHist$counts) 
  
} else if (input.units == 'm') {
  Flow.length.vector <- na.omit(as.numeric(as.vector(Flow.length.stream)))
  RoutingHist <- hist(Flow.length.vector,breaks=seq(min(Flow.length.vector),max(Flow.length.vector),length.out=breaks.hist+1))
  RoutingTable$flow.len <- RoutingHist$breaks[-1]
  RoutingTable$prop <- RoutingHist$counts/sum(RoutingHist$counts)
} else {
  'invalid response, RoutingTable not calculated.'
}

Step 7: Compile spatial data and compare original Spatial Function to outputs from this script.

We will create a list that compThe original DynatopSpatialFunctionExplicitReaches function outputs the following information:

  • DEM

    • Contains DEM raster name
  • Soils

    • Contains Soils raster name
  • layers - contains all layers to make HRUs

    • DEM

    • Soils

    • TWI (named atb)

  • disc - contains all elements to make the weighting matrix and groups table from the lumped parameterization

    • groups - the groups matrix

    • layers - same as above

    • chans - multiband raster with 1) channel labels (just equal to 1 for lumped) and 2) channel props, which describes the proportions of the cell which is occupied by the channel (here this can just be set equal to one if a 1.524-m raster is being used).

    • cuts - the number of cuts made to the layers

    • area.thresh - the area threshold below which HRUs will be lumped with other HRUs. This isn’t utilized here and can be set equal to 0.

    • hru - the HRU raster generated from the discretize process

    • weights - the weighting matrix

  • RoutingTable - a routing table showing the flow distance to the outlet and the proportion of channel cells that fall within the distance bin - this had 30 cuts

  • explicit.disc - contains all elements to make the weighting matrix and groups table from the explicit parameterization

    • groups - explicit groups matrix

    • layers - should be more or less the same as disc

    • chans - This time should be explicit with respect to Reach ID

    • cuts - should be the same as disc

    • area.thresh - should be same as disc

    • hru - This will be different than disc bc there should be explicit representations for each reach

    • weights - explicit weighting matrix

  • explicit.RoutingTable - same as above, this had 5 cuts, however

  • explicit.ChanTable - this is a table containing all of the reach data, including: “link_no,” “ds_link,” “us_link_1,” “us_link_2,” “stream_order,” “stream_length_ft,” “stream_drop_ft,” “stream_slope_ftpft,” “stream_straightline_length_ft,” “US_area_m2,” “width_m2”

# DEM
DEM <- dem

# Soils
Soils <- soils

# Layers 
layers <- raster.stack

# disc
disc <- list()
disc$groups <- groups.lump
disc$layers <- layers
disc$chans <- addLayer(stream.lump,stream.lump)
names(disc$chans) <- c('chans','chanprops')
disc$cuts <- data.frame(matrix(nrow=1,ncol=length(names(layers))))
colnames(disc$cuts) <- names(layers)
for (i in names(disc$cuts)) {print(i);disc$cuts[1,i] <- length(unique(layers[[i]]))}
[1] "TWI.dinf.cut"
[1] "soils"
[1] "elev.bands"
disc$cuts <- as.matrix(disc$cuts)
disc$area.thresh <- 0
disc$hru <- HRU.lump
weighting.matrix.lump.in <- weighting.matrix.lump
colnames(weighting.matrix.lump.in)[1] <- 'R'
rownames(weighting.matrix.lump.in)[1] <- 'R'
disc$weights <- as.matrix(weighting.matrix.lump.in)

# RoutingTable
RoutingTable <- RoutingTable

# explicit.disc
explicit.disc <- list()
explicit.disc$groups <- groups.explicit
explicit.disc$layers <- layers
explicit.disc$chans <- addLayer(stream,stream.lump)
names(explicit.disc$chans) <- c('chans','chanprops')
explicit.disc$cuts <- disc$cuts
explicit.disc$area.thresh <- 0
explicit.disc$hru <- HRU
weighting.matrix.explicit.in <- weighting.matrix
names.explicit.matrix <- paste0('R',unique(stream))
rownames(weighting.matrix.explicit.in)[1:length(unique(stream))] <- names.explicit.matrix
colnames(weighting.matrix.explicit.in)[1:length(unique(stream))] <- names.explicit.matrix
explicit.disc$weights <- as.matrix(weighting.matrix.explicit.in)

# explicit.RoutingTable (unused)
explicit.RoutingTable <- RoutingTable

# explicit.ChanTable
drn <- shapefile('SpatialInputData/FR4000StreamNet.shp')
bkf.width <- 12.16*(drn$USAreaM2/1000^2*0.386102)^0.42*0.3048  # estimate the width of the channel using the regional curves from Ashland Berry's thesis. Note this isn't perfect bc this equation is being extrapolated to smaller channels.
explicit.ChanTable <- as.data.frame(cbind('link_no'= drn$LINKNO,'ds_link'= drn$DSLINKNO,
                                    'us_link_1' = drn$USLINKNO1,'us_link_2'=drn$USLINKNO2,
                                    'stream_order'= drn$strmOrder,
                                    'stream_length_m' =drn$length_m,
                                    'stream_length_ft'=drn$Length,
                                    'stream_drop_ft'=drn$strmDrop,
                                    'stream_slope_ftpft'=drn$Slope,
                                    'stream_straightline_length_ft'=drn$StraightL,
                                    'US_area_m2'=drn$USAreaM2,
                                    'width_m2' = bkf.width))

# Compile the final spatial table
dynatop.spatial <- list('DEM'=DEM,'Soils'=Soils,'layers'=layers,'disc'=disc,
                        'RoutingTable'=RoutingTable,'explicit.disc'=explicit.disc,
                        'explicit.RoutingTable'=explicit.RoutingTable,
                        'explicit.ChanTable'=explicit.ChanTable)

Appendix A: Unused code

This chunk is a scratch workspace for old code

tot.cells <- data.frame(matrix(nrow=no.HRU.lump,ncol=2))
names(tot.cells) <- c('tot.cells','tot.cells.2')

for (j in 1:no.HRU.lump) {
  print(list.HRU[j])
  tot.cells$tot.cells[j] <- sum((HRU.lump.matrix==list.HRU[1])&(fdr.downstream.lump.matrix==list.HRU[j]),na.rm=T)
  #tot.cells$tot.cells.2[j] <- length(which((HRU.lump.matrix==unique(HRU.lump)[1])&(fdr.downstream.matrix==unique(HRU.lump)[j])))
  
}

parallel::detectCores()
n.cores <- parallel::detectCores() - 1
#create the cluster
my.cluster <- parallel::makeCluster(
  n.cores, 
  type = "PSOCK"
  )
#check cluster definition (optional)
print(my.cluster)

#register it to be used by %dopar%
doParallel::registerDoParallel(cl = my.cluster)

#check if it is registered (optional)
foreach::getDoParRegistered()

get.ds.cells <- function(i,HRU.matrix,HRU,fdr.downstream.matrix) {
  col <- matrix(nrow=1,length(unique(HRU)))
  for (j in 1:length(unique(HRU))) {
    temp.col <- length(which((HRU.matrix==unique(HRU)[i])&(fdr.downstream.matrix==unique(HRU)[j])))
    col[j] <- temp.col
  }
  return(col)
}

start.time <- Sys.time()
downstream.weighting.matrix.test <- foreach(i=1:length(unique(HRU)),
                                       .combine =rbind) %dopar% {
                                         temp.col <- get.ds.cells(i,HRU.matrix,HRU,fdr.downstream.matrix)
                                       }

end.time <- Sys.time()
end.time-start.time

stopCluster(cl = my.cluster)

options("scipen"=100, "digits"=5)

rownames(downstream.weighting.matrix.test) <- unique(HRU)
colnames(downstream.weighting.matrix.test) <- unique(HRU)

weighting.matrix.test <- downstream.weighting.matrix.test/rowSums(downstream.weighting.matrix.test)

rownames(weighting.matrix.test) <- unique(HRU)
colnames(weighting.matrix.test) <- unique(HRU)
LS0tDQp0aXRsZTogIlJ1biBNb2R1bGFyIERpc2NyZXRpemF0aW9uIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KVGhpcyBpcyBhbiBbUiBNYXJrZG93bl0oaHR0cDovL3JtYXJrZG93bi5yc3R1ZGlvLmNvbSkgTm90ZWJvb2suIFdoZW4geW91IGV4ZWN1dGUgY29kZSB3aXRoaW4gdGhlIG5vdGVib29rLCB0aGUgcmVzdWx0cyBhcHBlYXIgYmVuZWF0aCB0aGUgY29kZS4NCg0KIyBJbnRyb2R1Y3Rpb24NCg0KVGhlIG9iamVjdGl2ZSBvZiB0aGlzIG5vdGVib29rIGlzIHRvIGRpc2NyZXRpemUgSFJVcyBpbiBhIHdhdGVyc2hlZCBhbmQgY3JlYXRlIGEgd2VpZ2h0aW5nIG1hdHJpeCB0aGF0IGRlc2NyaWJlcyB0aGUgaGlsbHNsb3BlLXN0cmVhbSBuZXR3b3JrIGNvbm5lY3Rpdml0eSBpbiBhIGNhdGNobWVudC4gVGhpcyBub3RlYm9vayBpcyBtZWFudCB0byByZXBsYWNlIHRoZSBzcGF0aWFsIGRpc2NyZXRpemF0aW9uIHdvcmtmbG93ICgqRHluYXRvcFNwYXRpYWxGdW5jdGlvbkV4cGxpY2l0UmVhY2hlcyopIGZvciB0aGUgbW9kaWZpZWQgZHluYW1pYyBUT1BNT0RFTCBkZXNjcmliZWQgYnkgTWFob25leSBldCBhbC4gKDIwMjIpICpKLiBIeWRyb2wqLg0KDQpUbyBydW4gdGhpcyBub3RlYm9vaywgaXQgaXMgbmVjZXNzYXJ5IHRvIGZpcnN0IGluc3RhbGwga2VybmVscyBmb3IgUHl0aG9uIGFuZCBSIGFuZCBpbnN0YWxsIGFyY3B5IHRvIHRoZSBlbnZpcm9ubWVudCBmcm9tIHdoaWNoIHRoZSBub3RlYm9vayBpcyBydW4gKHNlZSBpbnN0cnVjdGlvbnMgW2hlcmVdKGh0dHBzOi8vZ2l0aHViLmNvbS90eWxlci1tYWhvbmV5L21vZGVsaW5nLXN0cmVhbWZsb3ctcGVybWFuZW5jZS9ibG9iL21haW4vYXJjcHktc2V0dXAuaXB5bmIpKS4gSW5zdGFuY2VzIHdoZW4gc3dpdGNoaW5nIHRoZSBrZXJuZWwgaXMgbmVjZXNzYXJ5IHdpbGwgYmUgZGVub3RlZCBpbiBhIE1hcmtkb3duIGNlbGwuDQoNCiMjIFN0ZXAgMTogTG9hZCBpbiBuZWNlc3NhcnkgbGlicmFyaWVzIGZvciBib3RoIFIgYW5kIFB5dGhvbg0KDQpXZSB3aWxsIGJlIHVzaW5nIGJvdGggUiBhbmQgcHl0aG9uIGluIHRoaXMgZXhhbXBsZS4gUHl0aG9uIHdpbGwgYmUgY2FsbGVkIHZpYSB0aGUgcmV0aWN1bGF0ZSBwYWNrYWdlLiBXZSB3aWxsIGJlIHVzaW5nIHRoZSBweXRob24gaW5zdGFsbGF0aW9uIGZyb20gYXJjZ2lzIHByby4gQSBtYWpvciBhZHZhbnRhZ2Ugb2YgdXNpbmcgdGhlIFIgTm90ZWJvb2sgZG9jdW1lbnQgb3ZlciwgZm9yIGV4YW1wbGUsIEp1cHl0ZXIgTm90ZWJvb2sgaXMgdGhlIGFiaWxpdHkgdG8gc3RvcmUgdmFyaWFibGVzIGdlbmVyYXRlZCBieSBib3RoIHB5dGhvbiBhbmQgUiBpbiB0aGUgZW52aXJvbm1lbnQuIFRodXMgd2UgY2FuIHVzZSB2YXJpYWJsZXMgZ2VuZXJhdGVkIGluIFIgd2l0aCB0aGUgYXJjZ2lzIEFQSSwgaGF2ZSB0aGUgdXNlci1mcmllbmRsaW5lc3Mgb2YgUnN0dWRpbywgYW5kIGhhdmUgdGhlIHJlcGxpY2FiaWxpdHkgb2YgYSBOb3RlYm9vayBkb2N1bWVudC4NCg0KYGBge3J9DQpzZXR3ZCgnQzovVXNlcnMvZGF2aWQvT25lRHJpdmUvRGVza3RvcC9NYWhvbmV5IFJlc2VhcmNoLzItTWFob25leS1FUEEvbW9kZWxpbmctc3RyZWFtZmxvdy1wZXJtYW5lbmNlLycpDQoNCiMgTG9hZCBpbiBsaWJyYXJpZXMNCmxpYnJhcnkocmV0aWN1bGF0ZSkNCmxpYnJhcnkoc3ApDQpsaWJyYXJ5KHJhc3RlcikNCmxpYnJhcnkoZHluYXRvcG1vZGVsKQ0KbGlicmFyeSh0b3Btb2RlbCkNCmxpYnJhcnkoZWNidG9vbHMpDQpsaWJyYXJ5KGNsdXN0ZXIpDQpsaWJyYXJ5KGZhY3RvZXh0cmEpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoTmJDbHVzdCkNCmxpYnJhcnkoYmFzaWNDbEV2YWwpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KG1hcHZpZXcpDQpsaWJyYXJ5KGRvUGFyYWxsZWwpDQpsaWJyYXJ5KGZvcmVhY2gpDQp1c2VfcHl0aG9uKCdDOi9Qcm9ncmFtIEZpbGVzL0FyY0dJUy9Qcm8vYmluL1B5dGhvbi9lbnZzL2FyY2dpc3Byby1weTMvcHl0aG9uLmV4ZScscmVxdWlyZWQ9VCkNCmFyY3B5IDwtIGltcG9ydCgiYXJjcHkiKQ0KYXJjZ2lzIDwtIGltcG9ydCgiYXJjZ2lzIikNCnB5X21hdGggPC0gaW1wb3J0KCJtYXRoIikNCnB5X251bXB5IDwtIGltcG9ydCgibnVtcHkiKQ0KaXB5dGhvbiA8LSBpbXBvcnQoIklQeXRob24iKQ0KDQpgYGANCg0KIyMgU3RlcCAyOiBJbXBvcnQgUmFzdGVycyBhbmQgY2FsY3VsYXRlIFRXSQ0KDQpOb3RlOiBUaGlzIGNvZGUgd2lsbCBjYWxjdWxhdGUgdGhlIFRXSSB1c2luZyB0aGUgRDggZmxvdyBkaXJlY3Rpb24uIEl0IGlzIHBvc3NpYmxlIHRvIGltcG9ydCBhIERJTkYgb3IgTUZEIHJhc3RlciBoZXJlIGFzIHdlbGwgYW5kIHVzZSB0aGlzIHRvIHJ1biB0aGUgYW5hbHlzaXMsIGFzc3VtaW5nIHRoYXQgaXQgaXMgYWxyZWFkeSBpbiB0aGUgR0RCLg0KDQpgYGB7cn0NCiMgUmVhZCBpbiB0aGUgcmFzdGVycyB0aGF0IHdlJ2QgbmVlZCBmb3IgSFJVIGNyZWF0aW9uDQpkZW0gPC0gcmFzdGVyKCdTcGF0aWFsSW5wdXREYXRhL2ZyMW1ldGVyREVNLnRpZicpDQpzb2lscyA8LSByYXN0ZXIoJ1NwYXRpYWxJbnB1dERhdGEvRlJTb2lsczFtTWFzay50aWYnKQ0KVFdJLmRpbmYgPC0gcmFzdGVyKCdDOi9Vc2Vycy9kYXZpZC9PbmVEcml2ZS9EZXNrdG9wL01haG9uZXkgUmVzZWFyY2gvMi1NYWhvbmV5LUVQQS9tb2RlbGluZy1zdHJlYW1mbG93LXBlcm1hbmVuY2UvU3BhdGlhbElucHV0RGF0YS9mcl90d2lfZGluZi50aWYnKQ0KVFdJLmRpbmZbVFdJLmRpbmY9PTBdIDwtIE5BICMgV2UgZG8gdGhpcyBiZWNhdXNlIHRoZSBhcmVhcyBvdXRzaWRlIG9mIHRoZSByYXN0ZXIgaGF2ZSBhIHZhbHVlIDAgYW5kIHdlIHdhbnQgdGhlc2UgdG8gYmUgc2V0IHRvIE5BDQoNCg0KYGBgDQoNCiMjIFN0ZXAgMzogQ3JlYXRlIGEgSFJVIHJhc3RlciBiYXNlZCBvbiB0aGUgVFdJLCBzb2lscywgYW5kIG90aGVyIGRhdGEgdXNpbmcgUg0KDQpOb3csIHdlIHdpbGwgc3dpdGNoIG92ZXIgdG8gUiBhbmQgY3JlYXRlIGFuIEhSVSBtYXAgb2YgZm9yIHRoZSB3YXRlcnNoZWQuICoqTWFrZSBzdXJlIHRvIHN3aXRjaCB0aGUga2VybmVsIGZyb20gcHl0aG9uIChlc3JpKSB0byBSKioNCg0KIyMjIFN0ZXAgMy4xOiBJbnN0YWxsIGxpYnJhcmllcyBhbmQgcmVhZCBpbiByYXN0ZXIgZmlsZXMNCg0KUmVsZXZhbnQgbGlicmFyaWVzIGluY2x1ZGUgc3BhdGlhbCBhbmQgcmFzdGVyIGxpYnJhcmllcywgY2x1c3RlciBhbmFseXNlcywgYW5kIHRpZHl2ZXJzZS4gTm90ZSwgd2UgYXJlIGNyZWF0aW5nIEhSVXMgd2l0aCB0aGUgZm9sbG93aW5nIHJhc3RlcnM6DQoNCi0gICBERU0NCg0KLSAgIFNvaWxzDQoNCi0gICBUV0kNCg0KT3RoZXIgcmVsZXZhbnQgbGF5ZXJzIHRoYXQgKmNvdWxkKiBiZSB1c2VkIGluY2x1ZGU6DQoNCi0gICBMVUxDDQoNCi0gICBLc2F0DQoNCi0gICBQb3Jvc2l0eQ0KDQotICAgQXNwZWN0DQoNCi0gICBDdXJ2YXR1cmUNCg0KLSAgIFRlbXBlcmF0dXJlDQoNCi0gICBQcmVjaXBpdGF0aW9uDQoNCk5vdGU6IHRoZXJlIGlzIHNvbWUgZGVncmVlIG9mIGN1c3RvbWl6YWJpbGl0eSBoZXJlLiBBbnkgcmVsZXZhbnQgbGF5ZXIgY291bGQgYmUgYWRkZWQuDQoNCmBgYHtyfQ0KIyMgUnVuIEttZWFucyB0byBjcmVhdGUgSFJVcyB1c2luZyBlbGV2YXRpb24gYmFuZHMsIFRXSSwgYW5kIHNvaWxzIGRhdGENCnNldC5zZWVkKDEyMykNCg0KIyBEZWZpbmUgdGhlIG51bWJlciBvZiBjdXRzIGZvciBlYWNoIHJhc3RlciANCmN1dHMuZWxldiA8LSA0DQpjdXRzLlRXSSA8LSA0DQoNCiMgQ3V0IHRoZSBsYXllcnMgaW50byB0aGUgc3BlY2lmaWVkIG51bWJlciBvZiBiaW5zDQplbGV2LmJhbmRzIDwtIGN1dChkZW0sIGN1dHMuZWxldikNClRXSS5kaW5mLmN1dCA8LSBjdXQoVFdJLmRpbmYsY3V0cy5UV0kpDQoNCiMgQ29tYmluZSB0aGUgcmFzdGVycyBpbnRvIGEgc2luZ2xlIHN0YWNrDQpyYXN0ZXIuc3RhY2sgPC0gc3RhY2soVFdJLmRpbmYuY3V0LCBzb2lscywgZWxldi5iYW5kcykgDQpuYW1lcyhyYXN0ZXIuc3RhY2spIDwtIGMoJ1RXSS5kaW5mLmN1dCcsJ3NvaWxzJywnZWxldi5iYW5kcycpDQoNCiMgUnVuIHRoZSBLbWVhbnMgYW5hbHlzaXMgLSBrIGlzIHRoZSBudW1iZXIgb2YgY2x1c3RlcnMgdGhhdCB3aWxsIGJlIGdlbmVyYXRlZC4NCkhSVSA8LSByYXN0ZXIua21lYW5zKHJhc3Rlci5zdGFjaywgayA9IDIwLCBpdGVyLm1heD0xMCwgbnN0YXJ0ID0gMTAsIGdlbyA9IFQsIGdlby53ZWlnaHQgPSAxKQ0KDQppbWFnZShIUlUpDQoNCmBgYA0KDQojIyMgU3RlcCAzLjM6IENyZWF0ZSBhbiBlbGJvdyBwbG90IHNob3dpbmcgdGhlIG51bWJlciBvZiBIUlVzIHdoaWNoIHNob3VsZCBiZSBjcmVhdGVkDQoNClRoZSB0b3RhbCB3aXRoaW4gc3VtIG9mIHNxdWFyZXMgZm9yIGVhY2ggY2x1c3RlciBpcyB1c2VkIHRvIGVzdGltYXRlIHRoZSB2YXJpYWJpbGl0eSBmb3IgZWFjaCBpdGVyYXRpb24uDQoNCk5vdGU6IHRoaXMgY29kZSB0YWtlcyBhIGRlY2VudCBhbW91bnQgb2YgdGltZSB0byBydW4uDQoNCmBgYHtyfQ0Kd3NzLnJhc3RlciA8LSBmdW5jdGlvbihrKSB7DQogICAgIyBSdW4gdGhlIEstbWVhbnMgYW5hbHlzaXMNCiAgICBrbWVhbnMucmFzdGVyIDwtIHJhc3Rlci5rbWVhbnMocmFzdGVyLnN0YWNrLCBrLCBpdGVyLm1heD0xMCwgbnN0YXJ0ID0gMTAsIGdlbyA9IFQsIGdlby53ZWlnaHQgPSAxKQ0KICAgIA0KICAgICMgTG9hZCBpbiB0aGUgZGF0YSBhbmQgZ2V0IGludG8gdmVjdG9yIGZvcm0NCiAgICBIUlUudmVjdG9yIDwtIGRhdGEuZnJhbWUoJ2hydScgPSBhcy52ZWN0b3Ioa21lYW5zLnJhc3RlcikpDQogICAgVFdJLnZlY3RvciA8LSBkYXRhLmZyYW1lKCdUV0knID0gYXMudmVjdG9yKFRXSS5kaW5mLmN1dCkpDQogICAgc29pbHMudmVjdG9yIDwtIGRhdGEuZnJhbWUoJ3NvaWxzJyA9IGFzLnZlY3Rvcihzb2lscykpDQogICAgZWxldi5iYW5kcy52ZWN0b3IgPC0gZGF0YS5mcmFtZSgnRWxldi5iYW5kcycgPSBhcy52ZWN0b3IoZWxldi5iYW5kcykpDQogICAgDQogICAgIyBQdXQgdGhlIHJhc3RlciB2ZWN0b3JzIGludG8gYSBjbGVhbmVkIGRhdGEgZnJhbWUNCiAgICByYXN0ZXJzLmRmIDwtIGRhdGEuZnJhbWUoSFJVLnZlY3RvcixUV0kudmVjdG9yLHNvaWxzLnZlY3RvcixlbGV2LmJhbmRzLnZlY3RvcikNCiAgICByYXN0ZXJzLmRmLmNsZWFuIDwtIHJhc3RlcnMuZGZbY29tcGxldGUuY2FzZXMocmFzdGVycy5kZiksXQ0KICANCiAgICAjIENhbGN1bGF0ZSB0aGUgd2l0aGluIGNsdXN0ZXIgc3VtIG9mIHNxdWFyZXMNCiAgICB3aXRoaW4uc3MgPC0gd2NzcyhyYXN0ZXJzLmRmLmNsZWFuWywtMV0scmFzdGVycy5kZi5jbGVhblssMV0pDQogICAgDQogICAgIyBSZXR1cm4gdGhlIHRvdGFsIHdpdGhpbiBjbHVzdGVyIHN1bSBvZiBzcXVhcmVzDQogICAgcmV0dXJuKHdpdGhpbi5zcyRXQ1NTKQ0KfQ0KDQpzZXQuc2VlZCgxMjMpDQoNCiMgQ29tcHV0ZSBhbmQgcGxvdCB3aXRoaW4gc3VtIHNxdWFyZXMgZm9yIGsgPSAxIHRvIGsgPSA0MA0Kay52YWx1ZXMgPC0gMTozMA0KDQojIGV4dHJhY3Qgd2l0aGluIHN1bSBvZiBzcXVhcmVzIGZvciAxLTQwIGNsdXN0ZXJzDQp3c3NfdmFsdWVzIDwtIG1hcF9kYmwoay52YWx1ZXMsd3NzLnJhc3RlcikNCg0KIyBwbG90IHdpdGhpbiBzdW0gb2Ygc3F1YXJlcyB2cyBjbHVzdGVyIChIUlUpIG51bWJlcg0KcGxvdChrLnZhbHVlcyx3c3NfdmFsdWVzLHR5cGU9J2InLHBjaD0xOSxmcmFtZT1GLHhsYWI9J051bWJlciBvZiBjbHVzdGVycycseWxhYj0nVG90YWwgd2l0aGluLWNsdXN0ZXIgc3VtIG9mIHNxJykNCg0KYGBgDQoNCiMjIyBTdGVwIDMuNDogUGxvdCB0aGUgY2x1c3RlcnMgZm9yIHJlZmVyZW5jZQ0KDQpXZSB3aWxsIGxvb2sgYXQgdGhlIHZhcmlvdXMgY2x1c3RlcnMgY3JlYXRlZCBhbmQgaG93IHRoZXkgYXBwZWFyIHdpdGhpbiByZWxhdGlvbiB0byBvbmUgYW5vdGhlcg0KDQpgYGB7cn0NCiMgTG9hZCBpbiB0aGUgZGF0YSBhbmQgZ2V0IGludG8gdmVjdG9yIGZvcm0NCkhSVS52ZWN0b3IgPC0gZGF0YS5mcmFtZSgnaHJ1JyA9IGFzLnZlY3RvcihIUlUpKQ0KVFdJLnZlY3RvciA8LSBkYXRhLmZyYW1lKCdUV0knID0gYXMudmVjdG9yKFRXSS5kaW5mLmN1dCkpDQpUV0kubmMudmVjdG9yIDwtIGRhdGEuZnJhbWUoJ1RXSS5uYyc9YXMudmVjdG9yKFRXSS5kaW5mKSkNCnNvaWxzLnZlY3RvciA8LSBkYXRhLmZyYW1lKCdzb2lscycgPSBhcy52ZWN0b3Ioc29pbHMpKQ0KZWxldi5iYW5kcy52ZWN0b3IgPC0gZGF0YS5mcmFtZSgnRWxldi5iYW5kcycgPSBhcy52ZWN0b3IoZWxldi5iYW5kcykpDQplbGV2LmJhbmRzLm5jLnZlY3RvciA8LSBkYXRhLmZyYW1lKCdFbGV2LmJhbmRzLm5jJyA9IGFzLnZlY3RvcihkZW0pKQ0KDQojIFB1dCB0aGUgcmFzdGVyIHZlY3RvcnMgaW50byBhIGNsZWFuZWQgZGF0YSBmcmFtZQ0KcmFzdGVycy5kZiA8LSBkYXRhLmZyYW1lKEhSVS52ZWN0b3IsVFdJLnZlY3RvcixUV0kubmMudmVjdG9yLHNvaWxzLnZlY3RvcixlbGV2LmJhbmRzLnZlY3RvcixlbGV2LmJhbmRzLm5jLnZlY3RvcikNCnJhc3RlcnMuZGYuY2xlYW4gPC0gcmFzdGVycy5kZltjb21wbGV0ZS5jYXNlcyhyYXN0ZXJzLmRmKSxdDQoNCiMgUGxvdCB0aGUgZWxldiBiYW5kcyB2cyB0d2kgd2l0aCBIUlUgYXMgY29sb3INCmdncGxvdChkYXRhPXJhc3RlcnMuZGYuY2xlYW4sIGFlcyh4PVRXSS5uYyx5PUVsZXYuYmFuZHMubmMsY29sPWhydSkpK2dlb21fcG9pbnQoKQ0KDQojIE5vdGUgLSB0aGUgc3RyYWlnaHQgbGluZXMgYXJlIGR1ZSB0byB0aGUgY3V0cyB0aGF0IHdlIGNyZXRlZC4gV2UgY291bGQganVzdCBhcyBlYXNpbHkgbm90IGluY2x1ZGUgdGhlc2UgY3V0cyBhbmQgb3RoZXIgZ2VvbWV0cmllcyB3b3VsZCBjb21lIHRocm91Z2gNCmBgYA0KDQojIyMgU3RlcCAzLjU6IFNvcnQgdGhlIEhSVXMgYnkgVFdJIGFuZCBhZGQgaW4gdGhlIHN0cmVhbSByYXN0ZXINCg0KSW4gdGhpcyBzdGVwLCB3ZSB3aWxsIGZpcnN0IHNvcnQgSFJVcyBieSBUV0kgdmFsdWUgKGhpZ2ggVFdJID0gaGlnaCBudW1iZXIgSFJVKSwgdGhlbiB3ZSB3aWxsICJidXJuIGluIiBhIHN0cmVhbSBuZXR3b3JrIHRoYXQgaGFzIGJlZW4gcHJldmlvdXNseSBkZWxpbmVhdGVkLiBUaGlzIHJlcHJlc2VudHMgdGhlIGdlb21vcnBoaWMgc3RyZWFtIGNoYW5uZWwsIHRvIHdoaWNoIHRoZSB3YXRlcnNoZWQgdXBsYW5kcyB3aWxsIGNvbnRyaWJ1dGUgd2F0ZXIuDQoNClRoZSByYXN0ZXIgbmVlZCB0byBoYXZlIHRoZSBmb2xsb3dpbmcgY2hhcmFjdGVyaXN0aWNzOg0KDQotICAgVGhlIHJlc29sdXRpb24sIGV4dGVudCwgYW5kIHNuYXBwaW5nIHNob3VsZCBiZSB0aGUgc2FtZSBhcyB0aGUgREVNDQoNCi0gICBUaGUgcmFzdGVyIGlzIGRlcml2ZWQgZnJvbSBhIEZsb3cgYWNjdW11bGF0aW9uIHJhc3RlciB3aXRoIGFuIHVwc3RyZWFtIGNvbnRyaWJ1dGluZyBhcmVhIHRocmVzaG9sZCBhcHBsaWVkDQoNCi0gICBUaGUgdGhyZXNob2xkIHNob3VsZCBjcmVhdGUgYSBzdHJlYW0gbmV0d29yayB0aGF0IGlzIHNpbWlsYXIgdG8gdGhlIHN0cmVhbSBtYXBwZWQgaW4gdGhlIGZpZWxkDQoNCi0gICBUaGUgcmVhY2hlcyBzaG91bGQgYmUgY3JlYXRlZCB1c2luZyB0aGUgc3RyZWFtIGxpbmsvb3JkZXIgdG9vbHMgaW4gQXJjR0lTDQoNCi0gICBUaGUgc2hhcGVmaWxlIGNyZWF0ZWQgZnJvbSB0aGVzZSBzdHJlYW0gdG9vbHMgc2hvdWxkIGJlIGJ1ZmZlcmVkIHN1Y2ggdGhhdCBhIGNvbnRpZ3VvdXMgc3RyZWFtIG5ldHdvcmsgcmFzdGVyIGNhbiBiZSBjcmVhdGVkDQoNCi0gICBIZXJlLCBhIGJ1ZmZlciBvZiAyIGNlbGwgd2lkdGhzIHdhcyB1c2VkIChcfjEuNTI0IFwqIDIgbSkNCg0KYGBge3J9DQojIyBDYWxjdWxhdGUgdGhlIG1lYW4gVFdJIGZvciBlYWNoIEhSVSAtIHJlbGFiZWwgdGhlIEhSVXMgc3VjaCB0aGF0IGhpZ2ggdmFsdWVkIFRXSSBpcyB0aGUgaGlnaGVzdCBudW1iZXIgSFJVIGFuZCBsb3cgdmFsdWVkIFRXSSBpcyB0aGUgbG93ZXN0IEhSVQ0KIyMgTm90ZSAtIHRoaXMgaXNuJ3QgbmVjZXNzYXJ5IGZvciBkeW5hdG9wbW9kZWwsIGJ1dCBtYXkgYmUgbmVjZXNzYXJ5IGZvciBkeW5hdG9wDQptZWFuLnR3aSA8LSBkYXRhLmZyYW1lKG1hdHJpeChucm93PWxlbmd0aCh1bmlxdWUoSFJVKSksbmNvbD01KSkNCm5hbWVzKG1lYW4udHdpKSA8LSBjKCdIUlUubmFtZScsJ2F0Yi5iYXIudW5zb3J0JywnaWQubmV3JywnYXRiLmJhcicsJ2lkJykNCm1lYW4udHdpJEhSVS5uYW1lIDwtIHVuaXF1ZShIUlUpDQoNCmhpbGxzbG9wZS5ocnUubWF0cml4IDwtIGFzLm1hdHJpeChIUlUpDQpUV0kuZGluZi5tYXRyaXggPC0gYXMubWF0cml4KFRXSS5kaW5mKQ0KDQptZWFuLnR3aSRhdGIuYmFyLnVuc29ydCA8LSB6b25hbCh4PVRXSS5kaW5mLHo9SFJVLGZ1bj0nbWVhbicpWywyXQ0KbWVhbi50d2kkYXRiLmJhciA8LSBzb3J0KG1lYW4udHdpJGF0Yi5iYXIudW5zb3J0LGRlY3JlYXNpbmc9RikNCm1lYW4udHdpJGlkLm5ldyA8LSBtYXRjaChtZWFuLnR3aSRhdGIuYmFyLnVuc29ydCxtZWFuLnR3aSRhdGIuYmFyKQ0KbWVhbi50d2kkaWQgPC0gMTpsZW5ndGgodW5pcXVlKEhSVSkpDQoNCnJlY2xhc3NpZnkubWF0cml4IDwtIGRhdGEuZnJhbWUobWF0cml4KG5yb3c9bGVuZ3RoKHVuaXF1ZShIUlUpKSxuY29sPTIpKQ0KbmFtZXMocmVjbGFzc2lmeS5tYXRyaXgpIDwtIGMoJ2lzJywnYmVjb21lcycpDQpyZWNsYXNzaWZ5Lm1hdHJpeCRpcyA8LSBtZWFuLnR3aSRIUlUubmFtZQ0KcmVjbGFzc2lmeS5tYXRyaXgkYmVjb21lcyA8LSBtZWFuLnR3aSRpZC5uZXcNCg0KSFJVIDwtIHJlY2xhc3NpZnkoSFJVLHJlY2xhc3NpZnkubWF0cml4KQ0KDQojIyBPdmVybGF5IHRoZSBzdHJlYW0gbmV0d29yayBhdG9wIHRoZSBIcnUgcmFzdGVyDQpzdHJlYW0gPC0gcmFzdGVyKCdTcGF0aWFsSW5wdXREYXRhL0ZSX3N0cmVhbV9uZXR3b3JrLnRpZicpDQpzdHJlYW1bc3RyZWFtPT0wXSA8LSBOQQ0KSFJVIDwtIEhSVSoxMCttYXgobWF4KHVuaXF1ZShzdHJlYW0pKSwxMDApICAgICAgICAgICAgICAgIyBVbmlxdWVseSBpZGVudGlmeWluZyB0aGUgdXBsYW5kIEhSVXMNCkhSVVtzdHJlYW0+MF0gPC0gc3RyZWFtW3N0cmVhbT4wXSAgICAgICAgICAgICAgICAgICAgICMgSWRlbnRpZnkgdGhlIGNlbGxzIGZyb20gdGhlIEhSVSByYXN0ZXIgdGhhdCBvdmVybGFwIHdpdGggdGhlIHN0cmVhbSwgc2V0IHRob3NlIGVxdWFsIHRvIHRoZSByZWFjaCBudW1iZXIgDQp3cml0ZVJhc3RlcihIUlUsIlNwYXRpYWxJbnB1dERhdGEvRlJfSFJVLnRpZiIsb3ZlcndyaXRlPVQpDQoNCmltYWdlKEhSVSkNCg0KdHdpLnpvbmFsIDwtIHpvbmFsKHg9VFdJLmRpbmYsej1IUlUsZnVuPSdtZWFuJykNClRXSS56b25hbCA8LSByZWNsYXNzaWZ5KEhSVSx0d2kuem9uYWwpDQoNCmltYWdlKFRXSS56b25hbCkNCg0Kc3RyZWFtLmx1bXAgPC0gc3RyZWFtDQpzdHJlYW0ubHVtcFtzdHJlYW0gPj0gMV0gPC0gMQ0KSFJVLmx1bXAgPC0gSFJVDQpIUlUubHVtcFtzdHJlYW0ubHVtcD4wXSA8LSBzdHJlYW0ubHVtcFtzdHJlYW0ubHVtcD4wXQ0KDQp3cml0ZVJhc3RlcihIUlUubHVtcCwiU3BhdGlhbElucHV0RGF0YS9GUl9IUlVfbHVtcC50aWYiLG92ZXJ3cml0ZT1UKQ0Kd3JpdGVSYXN0ZXIoc3RyZWFtLmx1bXAsIlNwYXRpYWxJbnB1dERhdGEvRlJfc3RyZWFtX2x1bXAudGlmIixvdmVyd3JpdGU9VCkNCg0KaW1hZ2UoSFJVLmx1bXApDQoNCmBgYA0KDQojIyMgU3RlcCAzLjYgVmlzdWFsaXplIHRoZSBIUlUvU3RyZWFtIFJhc3Rlcg0KDQpOb3RlIC0gSSdtIGdvaW5nIHRvIGRvIHRoaXMgd2l0aCB0aGUgbWFwdmlldyBmdW5jdGlvbiBpbiBSLCBidXQgdGhpcyBjb3VsZCBiZSBkb25lIHVzaW5nIHRoZSBhcmNweSBhbmQgYXJjZ2lzIG1vZHVsZXMgaW4gcHl0aG9uLiBUaGUgcmVhc29uIGZvciBub3QgdXNpbmcgcHl0aG9uIGlzIGJlY2F1c2UgaWYgd2UgaW50ZXJydXB0IHRoZSBrZXJuZWwgd2UnbGwgaGF2ZSB0byByZWxvYWQgdmFyaWFibGVzIGZyb20gYWJvdmUNCg0KYGBge3J9DQptYXBWaWV3KEhSVSwgbWF4cGl4ZWxzID0gIDIxMDIwMDApICsgbWFwVmlldyhUV0kuem9uYWwsIG1heHBpeGVscyA9ICAyMTAyMDAwKSArIA0KICBtYXBWaWV3KEhSVS5sdW1wLG1heHBpeGVscyA9ICAyMTAyMDAwKSArIG1hcFZpZXcoc3RyZWFtLmx1bXAsIG1heHBpeGVscyA9ICAyMTAyMDAwKSArDQogIG1hcFZpZXcoc3RyZWFtLG1heHBpeGVscyA9ICAyMTAyMDAwKQ0KDQpgYGANCg0KIyMgU3RlcCA0OiBDcmVhdGUgYSBmbG93IHdlaWdodGluZyByYXN0ZXINCg0KSW4gdGhpcyBzdGVwLCB3ZSB3aWxsIGNyZWF0ZSB0aGUgd2VpZ2h0aW5nIG1hdHJpeCB0byBydW4gdGhlIGR5bmF0b3Btb2RlbCBhbmFseXNpcy4gV2UgZG8gdGhpcyB0d2ljZS4gT25jZSBmb3IgdGhlICJFeHBsaWNpdCBtYXRyaXgiIHdoaWNoIHNob3dzIHRoZSBjb25uZWN0aXZpdHkgb2YgdXBsYW5kIGNlbGxzIHRoYXQgY29udHJpYnV0ZSB0byBhIHNwZWNpZmljIHJlYWNoLiBBIHNlY29uZCB0aW1lIGZvciB0aGUgIkx1bXBlZCBNYXRyaXgiIHdoaWNoIHNob3dzIHRoZSBjb25uZWN0aXZpdHkgb2YgY2VsbHMgdG8gYSAibHVtcGVkIiBzdHJlYW0gbmV0d29yay4gQXMgb2YgOC8yLzIwMjIsIHRoZSAiZHluYXRvcG1vZGVsIiBwYWNrYWdlIGlzIG5vdCBjb21wYXRpYmxlIHdpdGggZXhwbGljaXQgcmVhY2hlcy4NCg0KIyMjIFN0ZXAgNC4xOiBDYWxjdWxhdGUgdGhlIGV4cGxpY2l0IGRvd25zdHJlYW0gbWF0cml4DQoNClRoaXMgcHJvY2VzcyByZXR1cm5zIHRoZSB2YWx1ZSBvZiB0aGUgSFJVIGltbWVkaWF0ZWx5IGRvd25zdHJlYW0gb2YgYSBjZWxsLiBUaGlzIGFsZ29yaXRobSBzdHJpY3RseSB3b3JrcyBmb3IgYSBEOCBmbG93IGRpcmVjdGlvbiwgYnV0IGNvdWxkIGJlIGV4cGFuZGVkIGZvciBESW5mIGF0IGEgbGF0ZXIgdGltZS4NCg0KKipNYWtlIHN1cmUgdG8gc3dpdGNoIG92ZXIgdG8gdGhlIFIga2VybmVsKioNCg0KYGBge3J9DQojIEZpcnN0LCBjcmVhdGUgdGhlIGV4cGxpY2l0IG1hdHJpeA0KbGlicmFyeShzcCkNCmxpYnJhcnkocmFzdGVyKQ0KDQpIUlUgPC0gcmFzdGVyKCdTcGF0aWFsSW5wdXREYXRhL0ZSX0hSVS50aWYnKQ0KZmRyIDwtIHJhc3RlcignU3BhdGlhbElucHV0RGF0YS9mcl9mZHInKQ0KDQoNCkhSVS5tYXRyaXggPC0gYXMubWF0cml4KEhSVSkNCmZkci5tYXRyaXggPC0gYXMubWF0cml4KGZkcikNCg0KZmRyLmRvd25zdHJlYW0ubWF0cml4IDwtIG1hdHJpeChucm93PW5yb3coSFJVLm1hdHJpeCksbmNvbD1uY29sKEhSVS5tYXRyaXgpKQ0KDQpmb3IgKGkgaW4gMTpucm93KEhSVS5tYXRyaXgpKSB7DQogIGZvciAoaiBpbiAxOm5jb2woSFJVLm1hdHJpeCkpIHsNCiAgICBpZiAoaXMubmEoZmRyLm1hdHJpeFtpLGpdKSkgew0KICAgICAgZmRyLmRvd25zdHJlYW0ubWF0cml4W2ksal0gPC0gTkENCiAgICB9IGVsc2Ugew0KICAgICAgaWYgKGZkci5tYXRyaXhbaSxqXT09MSkgew0KICAgICAgICBmZHIuZG93bnN0cmVhbS5tYXRyaXhbaSxqXSA8LSBIUlUubWF0cml4W2ksaisxXQ0KICAgICAgfSBlbHNlIGlmIChmZHIubWF0cml4W2ksal09PTIpIHsNCiAgICAgICAgZmRyLmRvd25zdHJlYW0ubWF0cml4W2ksal0gPC0gSFJVLm1hdHJpeFtpKzEsaisxXQ0KICAgICAgfSBlbHNlIGlmIChmZHIubWF0cml4W2ksal09PTQpIHsNCiAgICAgICAgZmRyLmRvd25zdHJlYW0ubWF0cml4W2ksal0gPC0gSFJVLm1hdHJpeFtpKzEsal0NCiAgICAgIH0gZWxzZSBpZiAoZmRyLm1hdHJpeFtpLGpdPT04KSB7DQogICAgICAgIGZkci5kb3duc3RyZWFtLm1hdHJpeFtpLGpdIDwtIEhSVS5tYXRyaXhbaSsxLGotMV0NCiAgICAgIH0gZWxzZSBpZiAoZmRyLm1hdHJpeFtpLGpdPT0xNikgew0KICAgICAgICBmZHIuZG93bnN0cmVhbS5tYXRyaXhbaSxqXSA8LSBIUlUubWF0cml4W2ksai0xXQ0KICAgICAgfSBlbHNlIGlmIChmZHIubWF0cml4W2ksal09PTMyKSB7DQogICAgICAgIGZkci5kb3duc3RyZWFtLm1hdHJpeFtpLGpdIDwtIEhSVS5tYXRyaXhbaS0xLGotMV0NCiAgICAgIH0gZWxzZSBpZiAoZmRyLm1hdHJpeFtpLGpdPT02NCkgew0KICAgICAgICBmZHIuZG93bnN0cmVhbS5tYXRyaXhbaSxqXSA8LSBIUlUubWF0cml4W2ktMSxqXQ0KICAgICAgfSBlbHNlIGlmIChmZHIubWF0cml4W2ksal09PTEyOCkgew0KICAgICAgICBmZHIuZG93bnN0cmVhbS5tYXRyaXhbaSxqXSA8LSBIUlUubWF0cml4W2ktMSxqKzFdDQogICAgICB9IGVsc2Ugew0KICAgICAgICBmZHIuZG93bnN0cmVhbS5tYXRyaXhbaSxqXSA8LSBOQQ0KICAgICAgfQ0KICAgIH0NCiAgfQ0KfQ0KDQoNCmBgYA0KDQojIyMgU3RlcCA0LjI6IENhbGN1bGF0ZSB0aGUgZXhwbGljaXQgd2VpZ2h0aW5nIG1hdHJpeCB3aGljaCBzaG93cyB0aGUgbnVtYmVyIG9mIGNlbGxzIGZyb20gb25lIEhSVSB0byBjb250cmlidXRlIHRvIGRvd25zdHJlYW0gSFJVcy4NCg0KSW4gdGhpcyBzdGVwLCB3ZSB3aWxsIGNyZWF0ZSBhIHdlaWdodGluZyBtYXRyaXggdGhhdCBvdXRwdXRzIHRoZSBwcm9wb3J0aW9uIG9mIGZsb3cgZ2VuZXJhdGVkIGluIG9uZSBjZWxsIHRoYXQgd2lsbCBiZSByZWRpc3RyaWJ1dGVkIHRvIGRvd25zdHJlYW0gY2VsbHMuDQoNClRoaXMgd29ya3MgYnk6DQoNCi0gICBpZGVudGlmeWluZyBhbGwgY2VsbHMgdGhhdCBiZWxvbmcgdG8gYSBjZXJ0YWluIEhSVQ0KDQotICAgaWRlbnRpZnlpbmcgYWxsIGNlbGxzIHdoaWNoIGJlbG9uZyB0byBhIGRvd25zdHJlYW0gSFJVDQoNCi0gICBpZGVudGlmeWluZyB0aGUgY2VsbHMgd2hlcmUgYm90aCBvZiB0aGVzZSBjb25kaXRpb25zIGFyZSB0cnVlDQoNCi0gICByZXR1cm5pbmcgdGhlIG51bWJlciBvZiBjZWxscyB3aGVyZSBib3RoIGFyZSB0cnVlDQoNCkluIHRoaXMgcmVnYXJkLCB0aGUgb3V0cHV0IHdpbGwgYmUgYWxsIGNlbGxzIHdoaWNoIGJlbG9uZyB0byBIUlUgaSB0aGF0IGZsb3cgaW50byBIUlUgai4NCg0KTm90ZTogSSByZXdyb3RlIHNvbWUgb2YgdGhpcyBjb2RlLiBUaGUgcnVuIHRpbWUgZ29lcyBmcm9tIGFib3V0IDUwIG1pbnV0ZXMgdG8gYXBwcm94aW1hdGVseSA1MCBzZWNvbmRzLg0KDQpgYGB7cn0NCnByaW50KCdSdW5uaW5nIHdlaWdodGluZyBtYXRyaXguLi4nKQ0KDQpuby5IUlUuZXhwbGljaXQgPC0gbGVuZ3RoKHVuaXF1ZShIUlUpKQ0KSFJVLmV4cGxpY2l0Lmxpc3QgPC0gdW5pcXVlKEhSVSkNCg0KZG93bnN0cmVhbS53ZWlnaHRpbmcubWF0cml4IDwtIG1hdHJpeChucm93PW5vLkhSVS5leHBsaWNpdCxuY29sPW5vLkhSVS5leHBsaWNpdCkNCnN0YXJ0LnRpbWUgPC0gU3lzLnRpbWUoKQ0KDQpmb3IgKGkgaW4gMTpuby5IUlUuZXhwbGljaXQpIHsNCiAgcHJpbnQoSFJVLmV4cGxpY2l0Lmxpc3RbaV0pDQogIGZvciAoaiBpbiAxOm5vLkhSVS5leHBsaWNpdCkgew0KICAgIGRvd25zdHJlYW0ud2VpZ2h0aW5nLm1hdHJpeFtpLGpdIDwtIHN1bSgoSFJVLm1hdHJpeD09SFJVLmV4cGxpY2l0Lmxpc3RbaV0pJihmZHIuZG93bnN0cmVhbS5tYXRyaXg9PUhSVS5leHBsaWNpdC5saXN0W2pdKSxuYS5ybT1UKQ0KICB9DQp9DQoNCmVuZC50aW1lIDwtIFN5cy50aW1lKCkNCmVuZC50aW1lLXN0YXJ0LnRpbWUNCg0Kb3B0aW9ucygic2NpcGVuIj0xMDAsICJkaWdpdHMiPTUpDQoNCnJvd25hbWVzKGRvd25zdHJlYW0ud2VpZ2h0aW5nLm1hdHJpeCkgPC0gdW5pcXVlKEhSVSkNCmNvbG5hbWVzKGRvd25zdHJlYW0ud2VpZ2h0aW5nLm1hdHJpeCkgPC0gdW5pcXVlKEhSVSkNCg0Kd2VpZ2h0aW5nLm1hdHJpeCA8LSBkb3duc3RyZWFtLndlaWdodGluZy5tYXRyaXgvcm93U3Vtcyhkb3duc3RyZWFtLndlaWdodGluZy5tYXRyaXgpDQoNCnJvd25hbWVzKHdlaWdodGluZy5tYXRyaXgpIDwtIHVuaXF1ZShIUlUpDQpjb2xuYW1lcyh3ZWlnaHRpbmcubWF0cml4KSA8LSB1bmlxdWUoSFJVKQ0KDQpyb3dTdW1zKHdlaWdodGluZy5tYXRyaXgpDQoNCmBgYA0KDQojIyMgU3RlcCA0LjM6IEluaXRpYWxpemUgdGhlIGx1bXBlZCBkb3duc3RyZWFtIG1hdHJpeA0KDQpOb3RlIHRoYXQgdGhlIGN1cnJlbnQgaW1wbGVtZW50YXRpb24gb2YgZHluYXRvcG1vZGVsIGRvZXMgbm90IHN1cHBvcnQgdGhlIGV4cGxpY2l0IHN0cmVhbXMsIGFzIGZhciBhcyBJIGFtIGF3YXJlLiBEeW5hdG9wIG1heSwgYnV0IEkgaGF2ZW4ndCBpbnZlc3RpZ2F0ZWQgdGhlIG1vZGVsIGluIGRlcHRoIGFzIG9mIDgvMy8yMDIyDQoNCmBgYHtyfQ0KIyBOZXh0LCBjcmVhdGUgdGhlIGx1bXBlZCBtYXRyaXgNCg0KSFJVLmx1bXAgPC0gcmFzdGVyKCdTcGF0aWFsSW5wdXREYXRhL0ZSX0hSVV9sdW1wLnRpZicpDQpmZHIgPC0gcmFzdGVyKCdTcGF0aWFsSW5wdXREYXRhL2ZyX2ZkcicpDQoNCkhSVS5sdW1wLm1hdHJpeCA8LSBhcy5tYXRyaXgoSFJVLmx1bXApDQpmZHIubWF0cml4IDwtIGFzLm1hdHJpeChmZHIpDQoNCmZkci5kb3duc3RyZWFtLmx1bXAubWF0cml4IDwtIG1hdHJpeChucm93PW5yb3coSFJVLmx1bXAubWF0cml4KSxuY29sPW5jb2woSFJVLmx1bXAubWF0cml4KSkNCg0KZm9yIChpIGluIDE6bnJvdyhIUlUubHVtcC5tYXRyaXgpKSB7DQogIGZvciAoaiBpbiAxOm5jb2woSFJVLmx1bXAubWF0cml4KSkgew0KICAgIGlmIChpcy5uYShmZHIubWF0cml4W2ksal0pKSB7DQogICAgICBmZHIuZG93bnN0cmVhbS5sdW1wLm1hdHJpeFtpLGpdIDwtIE5BDQogICAgfSBlbHNlIHsNCiAgICAgIGlmIChmZHIubWF0cml4W2ksal09PTEpIHsNCiAgICAgICAgZmRyLmRvd25zdHJlYW0ubHVtcC5tYXRyaXhbaSxqXSA8LSBIUlUubHVtcC5tYXRyaXhbaSxqKzFdDQogICAgICB9IGVsc2UgaWYgKGZkci5tYXRyaXhbaSxqXT09Mikgew0KICAgICAgICBmZHIuZG93bnN0cmVhbS5sdW1wLm1hdHJpeFtpLGpdIDwtIEhSVS5sdW1wLm1hdHJpeFtpKzEsaisxXQ0KICAgICAgfSBlbHNlIGlmIChmZHIubWF0cml4W2ksal09PTQpIHsNCiAgICAgICAgZmRyLmRvd25zdHJlYW0ubHVtcC5tYXRyaXhbaSxqXSA8LSBIUlUubHVtcC5tYXRyaXhbaSsxLGpdDQogICAgICB9IGVsc2UgaWYgKGZkci5tYXRyaXhbaSxqXT09OCkgew0KICAgICAgICBmZHIuZG93bnN0cmVhbS5sdW1wLm1hdHJpeFtpLGpdIDwtIEhSVS5sdW1wLm1hdHJpeFtpKzEsai0xXQ0KICAgICAgfSBlbHNlIGlmIChmZHIubWF0cml4W2ksal09PTE2KSB7DQogICAgICAgIGZkci5kb3duc3RyZWFtLmx1bXAubWF0cml4W2ksal0gPC0gSFJVLmx1bXAubWF0cml4W2ksai0xXQ0KICAgICAgfSBlbHNlIGlmIChmZHIubWF0cml4W2ksal09PTMyKSB7DQogICAgICAgIGZkci5kb3duc3RyZWFtLmx1bXAubWF0cml4W2ksal0gPC0gSFJVLmx1bXAubWF0cml4W2ktMSxqLTFdDQogICAgICB9IGVsc2UgaWYgKGZkci5tYXRyaXhbaSxqXT09NjQpIHsNCiAgICAgICAgZmRyLmRvd25zdHJlYW0ubHVtcC5tYXRyaXhbaSxqXSA8LSBIUlUubHVtcC5tYXRyaXhbaS0xLGpdDQogICAgICB9IGVsc2UgaWYgKGZkci5tYXRyaXhbaSxqXT09MTI4KSB7DQogICAgICAgIGZkci5kb3duc3RyZWFtLmx1bXAubWF0cml4W2ksal0gPC0gSFJVLmx1bXAubWF0cml4W2ktMSxqKzFdDQogICAgICB9IGVsc2Ugew0KICAgICAgICBmZHIuZG93bnN0cmVhbS5sdW1wLm1hdHJpeFtpLGpdIDwtIE5BDQogICAgICB9DQogICAgfQ0KICB9DQp9DQoNCg0KYGBgDQoNCiMjIyBTdGVwIDQuNDogQ2FsY3VsYXRlIHRoZSBsdW1wZWQgd2VpZ2h0aW5nIG1hdHJpeCB3aGljaCBzaG93cyB0aGUgbnVtYmVyIG9mIGNlbGxzIGZyb20gb25lIEhSVSB0byBjb250cmlidXRlIHRvIGRvd25zdHJlYW0gSFJVcy4NCg0KTm90ZTogSSBoYXZlIHJld3JpdHRlbiB0aGUgY29kZSBoZXJlLiBUaGUgcHJldmlvdXMgcnVuIHRpbWUgZm9yIHRoaXMgY2h1bmsgd2FzIDEwIG1pbnV0ZXMuIE5vdyBpdCBpcyBhYm91dCAxMCBzZWNvbmRzLg0KDQpgYGB7cn0NCnByaW50KCdSdW5uaW5nIHdlaWdodGluZyBtYXRyaXguLi4nKQ0KDQpuby5IUlUubHVtcCA8LSBsZW5ndGgodW5pcXVlKEhSVS5sdW1wKSkNCmxpc3QuSFJVLmx1bXAgPC0gdW5pcXVlKEhSVS5sdW1wKQ0KDQpkb3duc3RyZWFtLndlaWdodGluZy5tYXRyaXgubHVtcCA8LSBtYXRyaXgobnJvdz1sZW5ndGgodW5pcXVlKEhSVS5sdW1wKSksbmNvbD1sZW5ndGgodW5pcXVlKEhSVS5sdW1wKSkpDQpzdGFydC50aW1lIDwtIFN5cy50aW1lKCkNCmZvciAoaSBpbiAxOm5vLkhSVS5sdW1wKSB7DQogIHByaW50KGxpc3QuSFJVLmx1bXBbaV0pDQogIGZvciAoaiBpbiAxOm5vLkhSVS5sdW1wKSB7DQogICAgZG93bnN0cmVhbS53ZWlnaHRpbmcubWF0cml4Lmx1bXBbaSxqXSA8LSBzdW0oKEhSVS5sdW1wLm1hdHJpeD09bGlzdC5IUlUubHVtcFtpXSkmKGZkci5kb3duc3RyZWFtLmx1bXAubWF0cml4PT1saXN0LkhSVS5sdW1wW2pdKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYS5ybT1UKQ0KICB9DQp9DQplbmQudGltZSA8LSBTeXMudGltZSgpDQplbmQudGltZS1zdGFydC50aW1lDQoNCm9wdGlvbnMoInNjaXBlbiI9MTAwLCAiZGlnaXRzIj01KQ0KDQpyb3duYW1lcyhkb3duc3RyZWFtLndlaWdodGluZy5tYXRyaXgubHVtcCkgPC0gdW5pcXVlKEhSVS5sdW1wKQ0KY29sbmFtZXMoZG93bnN0cmVhbS53ZWlnaHRpbmcubWF0cml4Lmx1bXApIDwtIHVuaXF1ZShIUlUubHVtcCkNCg0Kd2VpZ2h0aW5nLm1hdHJpeC5sdW1wIDwtIGRvd25zdHJlYW0ud2VpZ2h0aW5nLm1hdHJpeC5sdW1wL3Jvd1N1bXMoZG93bnN0cmVhbS53ZWlnaHRpbmcubWF0cml4Lmx1bXApDQoNCnJvd25hbWVzKHdlaWdodGluZy5tYXRyaXgubHVtcCkgPC0gdW5pcXVlKEhSVS5sdW1wKQ0KY29sbmFtZXMod2VpZ2h0aW5nLm1hdHJpeC5sdW1wKSA8LSB1bmlxdWUoSFJVLmx1bXApDQoNCnJvd1N1bXMod2VpZ2h0aW5nLm1hdHJpeC5sdW1wKQ0KDQpgYGANCg0KIyMgU3RlcCA1OiBDcmVhdGUgdGhlIEdyb3VwcyBNYXRyaXgNCg0KTmV4dCwgd2Ugd2lsbCBjcmVhdGUgYSAiR3JvdXBzIiBtYXRyaXggd2hpY2ggd2lsbCBzcGVjaWZ5IHRoZSBpbml0aWFsIHBhcmFtZXRlcnMgZm9yIGR5bmFtaWMgVE9QTU9ERUwuDQoNCk5vdGUgLSBJIGRpZCBub3RpY2Ugc29tZSBkaWZmZXJlbmNlcyBiZXR3ZWVuIGF0Yi5iYXIgZnJvbSB0aGUgMjAgSFJVcyBkaXNjcmV0aXplZCBoZXJlIGFuZCB0aGUgMi0gSFJVcyBkaXNjcmV0aXplZCB3aXRoIGR5bmF0b3Btb2RlbC4gSXQgaXMgcmVsYXRlZCB0byBob3cgdGhlIEhSVXMgYXJlIGRpc2NyZXRpemVkLiBUaGVyZSBpcyBtb3JlIHZhcmlhYmlsaXR5IHdpdGggdGhlIGR5bmF0b3Btb2RlbCBhcHByb2FjaC4NCg0KSXRlbXMgd2Ugd2lsbCBuZWVkIHRvIGluY2x1ZGUgaW4gdGhlIHRhYmxlOg0KDQotICAgaWQgLSB0aGUgSUQgb2YgdGhlIGNoYW5uZWwsIHR5cGljYWxseSBjaGFubmVscyBhcmUgcmFua2VkIDAtMTAwIGFuZCBIUlVzIGFyZSByYW5rZWQgYmFzZWQgb24gVFdJIHdpdGggSUQgXD4gMTAwDQoNCi0gICB0YWcgLSBzYW1lIGFzIGlkDQoNCi0gICBjaGFuLm5vIC0gdGhlIGNoYW5uZWwgbnVtYmVyIChub3RlIC0gb25seSBmb3IgSURzIGNsYXNzaWZpZWQgYXMgY2hhbm5lbHMpDQoNCi0gICBhcmVhX3BjIC0gdGhlIHBlcmNlbnQgYXJlYSB0aGF0IHRoZSBIUlUgdGFrZXMgdXANCg0KLSAgIGFyZWEgLSB0aGUgYXJlYSBtXF4yIHRoYXQgdGhlIEhSVSB0YWtlcyB1cA0KDQotICAgc2JhciAtIHRoZSBhdmVyYWdlIHNsb3BlIG9mIHRoZSBIUlUgKG0vbSkNCg0KLSAgIGF0Yi5iYXIgLSB0aGUgYXZlcmFnZSBUV0kgZm9yIHRoZSBIUlUNCg0KLSAgIGdhdWdlLmlkIC0gdGhlIHJhaW4gZ2F1Z2UgY29udHJpYnV0aW5nIHRvIHRoZSBIUlUNCg0KLSAgIGNhdGNoLmlkIC0gdGhlIGNhdGNobWVudCBpbiB3aGljaCB0aGUgSFJVIGlzIHNpdHVhdGVkDQoNCi0gICBzcnpfbWF4IC0gbWF4aW11bSBkZXB0aCBvZiByb290IHpvbmUgcGFyYW1ldGVyIHZhbHVlIGluaXRpYWxpemVkIGFzIDAuMQ0KDQotICAgbG5fdDAgLSBsbiB0MCBwYXJhbWV0ZXIgdmFsdWUgaW5pdGlhbGl6ZWQgYXMgNw0KDQotICAgbSAtIGV4cG9uZW50aWFsIGRlY2xpbmUgb2YgdHJhbnNtaXNzaXZpdHkgcGFyYW1ldGVyIGluaXRpYWxpemVkIGFzIDAuMDENCg0KLSAgIHNyejAgLSBpbml0aWFsIHJvb3Qgem9uZSBzdG9yYWdlIHBhcmFtZXRlciBpbml0aWFsaXplZCBhcyAwDQoNCi0gICB0ZCAtIHRkIHBhcmFtZXRlciBpbml0aWFsaXplZCBhcyAxDQoNCi0gICB2Y2hhbiAtIHYgY2hhbiBwYXJhbWV0ZXIgKGZvciBjaGFubmVscyBvbmx5KSBwYXJhbWV0ZXJpemVkIGFzIDEwMDANCg0KLSAgIHZvZiAtIG92ZXJsYW5kIHYgcGFyYW1ldGVyIChmb3IgSFJVcyBvbmx5KSBwYXJhbWV0ZXJpemVkIGFzIDEwMA0KDQotICAgazAgLSBrMCBwYXJhbWV0ZXIgaW5pdGlhbGl6ZWQgYXMgMWUrOA0KDQotICAgQ0QgLSBDRCBwYXJhbWV0ZXIgKHVudXNlZCkgaW5pdGlhbGl6ZWQgYXMgMC4xDQoNCi0gICBzZF9tYXggLSBzZF9tYXggcGFyYW1ldGVyIGluaXRpYWxpemVkIGFzIDAuNQ0KDQotICAgcGVfZmFjdCAtIHBvdGVudGlhbCBldmFwb3RyYW5zcGlyYXRpb24gZmFjdG9yIHVzZWQgdG8gc2NhbGUgUEVULCBpbml0aWFsaXplZCBhcyAxDQoNCi0gICB2b2ZfZmFjdCAtIHZvZiBmYWN0b3IgaW5pdGlhbGl6ZWQgYXMgMQ0KDQotICAgcmFpbl9mYWN0IC0gcmFpbiBmYWN0b3IgdXNlZCB0byBzY2FsZSBwcmVjaXAgYW1vdW50LCBpbml0aWFsaXplZCBhcyAxDQoNCi0gICBtYW5uLm4gLSBtYW5uaW5ncyBuIGluaXRpYWxpemVkIGFzIDAuMDENCg0KLSAgIFMwIC0gaW5pdGlhbCBzbG9wZSwgcGFyYW1ldGVyaXplZCBhcyAwLjENCg0KLSAgIGV4X21heCAtIGV4IG1heCBwYXJhbWV0ZXJpemVkIGFzIDENCg0KIyMjIFN0ZXAgNS4xIENyZWF0ZSB0aGUgZ3JvdXBzIG1hdHJpeCBmb3IgdGhlIGV4cGxpY2l0IHJlYWNoZXMNCg0KYGBge3J9DQojIEVudGVyIHRoZSBjZWxsIHNpemUgDQpjZWxsLnNpemUgPC0gYXMubnVtZXJpYyhyZWFkbGluZSgnRW50ZXIgdGhlIHJlc29sdXRpb24gb2YgdGhlIHJhc3RlciBpbiBtIChmb3IgMTAtbSByYXN0ZXIgZW50ZXIgMTApOiAnKSkNCg0KIyBDcmVhdGUgZnVuY3Rpb25zIHRvIGRldGVybWluZSB0aGUgYXJlYSBvZiB0aGUgcmFzdGVyDQphcmVhX2NlbGwgPC0gZnVuY3Rpb24ocmFzdGVyLm1hdHJpeCxyYXN0ZXJfSUQpIHsNCiAgYXJlYV9jIDwtIHN1bShyYXN0ZXIubWF0cml4PT1yYXN0ZXJfSUQsIG5hLnJtPVQpDQogIHJldHVybihhcmVhX2MpDQp9DQoNCiMgQ3JlYXRlIGZ1bmN0aW9uIHRvIGRldGVybWluZSB0aGUgem9uYWwgbWVhbiAodW51c2VkKQ0KbWVhbl9yYXN0ZXIgPC0gZnVuY3Rpb24ocmFzdGVyLm1hdHJpeCwgSFJVLm1hdHJpeCwgcmFzdGVyX0lEKSB7DQogIG1lYW5fcmFzdCA8LSBtZWFuKHJhc3Rlci5tYXRyaXhbSFJVLm1hdHJpeD09cmFzdGVyX0lEXSxuYS5ybT1UKQ0KICByZXR1cm4obWVhbl9yYXN0KQ0KfQ0KDQojIENhbGN1bGF0ZSB0aGUgc2xvcGUgDQpzbG9wZSA8LSB0ZXJyYWluKGRlbSxvcHQ9J3Nsb3BlJyx1bml0cz0ndGFuZ2VudCcpDQoNCiMgSW5pdGlhbCBpemUgdGhlIGdyb3VwcyANCmdyb3Vwcy5leHBsaWNpdCA8LSBkYXRhLmZyYW1lKG1hdHJpeChucm93PWxlbmd0aCh1bmlxdWUoSFJVKSksbmNvbD0yNikpDQpuYW1lcyhncm91cHMuZXhwbGljaXQpIDwtIGMoJ2lkJywndGFnJywnY2hhbi5ubycsJ29yZGVyJywnYXJlYV9wYycsJ2FyZWEnLCdzYmFyJywnYXRiLmJhcicsJ2dhdWdlLmlkJywnY2F0Y2guaWQnLCdzcnpfbWF4JywnbG5fdDAnLCdtJywnc3J6MCcsJ3RkJywndmNoYW4nLCd2b2YnLCdrMCcsJ0NEJywnc2RfbWF4JywncGVfZmFjdCcsJ3ZvZl9mYWN0JywncmFpbl9mYWN0JywnbWFubi5uJywnUzAnLCdleF9tYXgnKQ0KDQojIEFzc2lnbiB0aGUgZ3JvdXBzDQpncm91cHMuZXhwbGljaXQkaWQgPC0gdW5pcXVlKEhSVSkNCmdyb3Vwcy5leHBsaWNpdCR0YWcgPC0gdW5pcXVlKEhSVSkNCmdyb3Vwcy5leHBsaWNpdCRjaGFuLm5vWzE6bGVuZ3RoKHVuaXF1ZShzdHJlYW0pKV0gPC0gdW5pcXVlKHN0cmVhbSkNCmdyb3Vwcy5leHBsaWNpdCRvcmRlciA8LSAxOmxlbmd0aCh1bmlxdWUoSFJVKSkNCmdyb3Vwcy5leHBsaWNpdCRhcmVhX3BjIDwtIHNhcHBseShYPWdyb3Vwcy5leHBsaWNpdCRpZCxGVU49YXJlYV9jZWxsLHJhc3Rlci5tYXRyaXg9SFJVLm1hdHJpeCkvc3VtKCFpcy5uYShIUlUubWF0cml4KSkqMTAwDQpncm91cHMuZXhwbGljaXQkYXJlYSA8LSBncm91cHMuZXhwbGljaXQkYXJlYV9wYypzdW0oIWlzLm5hKEhSVS5tYXRyaXgpKSpjZWxsLnNpemVeMi8xMDANCmdyb3Vwcy5leHBsaWNpdCRzYmFyIDwtIHpvbmFsKHg9c2xvcGUsej1IUlUsZnVuPSdtZWFuJylbLDJdDQpncm91cHMuZXhwbGljaXQkYXRiLmJhclsoMStsZW5ndGgodW5pcXVlKHN0cmVhbSkpKTpsZW5ndGgodW5pcXVlKEhSVSkpXSA8LSB6b25hbCh4PVRXSS5kaW5mLHo9SFJVLGZ1bj0nbWVhbicpWygxK2xlbmd0aCh1bmlxdWUoc3RyZWFtKSkpOmxlbmd0aCh1bmlxdWUoSFJVKSksMl0NCmdyb3Vwcy5leHBsaWNpdCRnYXVnZS5pZCA8LSAxDQpncm91cHMuZXhwbGljaXQkY2F0Y2guaWQgPC0gMQ0KZ3JvdXBzLmV4cGxpY2l0JHNyel9tYXggPC0gMC4xDQpncm91cHMuZXhwbGljaXQkbG5fdDAgPC0gNw0KZ3JvdXBzLmV4cGxpY2l0JG0gPC0gMC4wMQ0KZ3JvdXBzLmV4cGxpY2l0JHNyejAgPC0gMA0KZ3JvdXBzLmV4cGxpY2l0JHRkIDwtIDENCmdyb3Vwcy5leHBsaWNpdCR2Y2hhblsxOmxlbmd0aCh1bmlxdWUoc3RyZWFtKSldIDwtIDEwMDANCmdyb3Vwcy5leHBsaWNpdCR2b2ZbKDErbGVuZ3RoKHVuaXF1ZShzdHJlYW0pKSk6bGVuZ3RoKHVuaXF1ZShIUlUpKV0gPC0gMTAwDQpncm91cHMuZXhwbGljaXQkazAgPC0gMWUrOA0KZ3JvdXBzLmV4cGxpY2l0JENEIDwtIDAuMQ0KZ3JvdXBzLmV4cGxpY2l0JHNkX21heCA8LSAwLjUNCmdyb3Vwcy5leHBsaWNpdCRwZV9mYWN0IDwtIDENCmdyb3Vwcy5leHBsaWNpdCR2b2ZfZmFjdCA8LSAxDQpncm91cHMuZXhwbGljaXQkcmFpbl9mYWN0IDwtIDENCmdyb3Vwcy5leHBsaWNpdCRtYW5uLm4gPC0gMC4wMQ0KZ3JvdXBzLmV4cGxpY2l0JFMwIDwtIDAuMQ0KZ3JvdXBzLmV4cGxpY2l0JGV4X21heCA8LSAxDQpgYGANCg0KIyMjIFN0ZXAgNS4yOiBDcmVhdGUgdGhlIGdyb3VwcyBtYXRyaXggZm9yIHRoZSBsdW1wZWQgcmVhY2hlcw0KDQpgYGB7cn0NCg0KIyBDYWxjdWxhdGUgdGhlIHNsb3BlIA0Kc2xvcGUgPC0gdGVycmFpbihkZW0sb3B0PSdzbG9wZScsdW5pdHM9J3RhbmdlbnQnKQ0KDQojIEluaXRpYWxpemUgdGhlIGdyb3VwcyANCmdyb3Vwcy5sdW1wIDwtIGRhdGEuZnJhbWUobWF0cml4KG5yb3c9bm8uSFJVLmx1bXAsbmNvbD0yNikpDQpuYW1lcyhncm91cHMubHVtcCkgPC0gYygnaWQnLCd0YWcnLCdjaGFuLm5vJywnb3JkZXInLCdhcmVhX3BjJywnYXJlYScsJ3NiYXInLCdhdGIuYmFyJywnZ2F1Z2UuaWQnLCdjYXRjaC5pZCcsJ3Nyel9tYXgnLCdsbl90MCcsJ20nLCdzcnowJywndGQnLCd2Y2hhbicsJ3ZvZicsJ2swJywnQ0QnLCdzZF9tYXgnLCdwZV9mYWN0Jywndm9mX2ZhY3QnLCdyYWluX2ZhY3QnLCdtYW5uLm4nLCdTMCcsJ2V4X21heCcpDQoNCiMgQXNzaWduIHRoZSBncm91cHMNCmdyb3Vwcy5sdW1wJGlkIDwtIGxpc3QuSFJVLmx1bXANCmdyb3Vwcy5sdW1wJHRhZyA8LSBsaXN0LkhSVS5sdW1wDQpncm91cHMubHVtcCRjaGFuLm5vWzE6bGVuZ3RoKHVuaXF1ZShzdHJlYW0ubHVtcCkpXSA8LSB1bmlxdWUoc3RyZWFtLmx1bXApDQpncm91cHMubHVtcCRvcmRlciA8LSAxOm5vLkhSVS5sdW1wDQpncm91cHMubHVtcCRhcmVhX3BjIDwtIHNhcHBseShYPWdyb3Vwcy5sdW1wJGlkLA0KICAgICAgICAgICAgICAgICAgICAgICAgIEZVTj1hcmVhX2NlbGwscmFzdGVyLm1hdHJpeD1IUlUubHVtcC5tYXRyaXgpL3N1bSghaXMubmEoSFJVLmx1bXAubWF0cml4KSkqMTAwDQpncm91cHMubHVtcCRhcmVhIDwtIGdyb3Vwcy5sdW1wJGFyZWFfcGMqc3VtKCFpcy5uYShIUlUubHVtcC5tYXRyaXgpKSpjZWxsLnNpemVeMi8xMDANCmdyb3Vwcy5sdW1wJHNiYXIgPC0gem9uYWwoeD1zbG9wZSx6PUhSVS5sdW1wLGZ1bj0nbWVhbicpWywyXQ0KZ3JvdXBzLmx1bXAkYXRiLmJhclsoMStsZW5ndGgodW5pcXVlKHN0cmVhbS5sdW1wKSkpOmxlbmd0aCh1bmlxdWUoSFJVLmx1bXApKV0gPC0gem9uYWwoeD1UV0kuZGluZix6PUhSVS5sdW1wLGZ1bj0nbWVhbicpWygxK2xlbmd0aCh1bmlxdWUoc3RyZWFtLmx1bXApKSk6bGVuZ3RoKHVuaXF1ZShIUlUubHVtcCkpLDJdDQpncm91cHMubHVtcCRnYXVnZS5pZCA8LSAxDQpncm91cHMubHVtcCRjYXRjaC5pZCA8LSAxDQpncm91cHMubHVtcCRzcnpfbWF4IDwtIDAuMQ0KZ3JvdXBzLmx1bXAkbG5fdDAgPC0gNw0KZ3JvdXBzLmx1bXAkbSA8LSAwLjAxDQpncm91cHMubHVtcCRzcnowIDwtIDANCmdyb3Vwcy5sdW1wJHRkIDwtIDENCmdyb3Vwcy5sdW1wJHZjaGFuWzE6bGVuZ3RoKHVuaXF1ZShzdHJlYW0ubHVtcCkpXSA8LSAxMDAwDQpncm91cHMubHVtcCR2b2ZbKDErbGVuZ3RoKHVuaXF1ZShzdHJlYW0ubHVtcCkpKTpsZW5ndGgodW5pcXVlKEhSVS5sdW1wKSldIDwtIDEwMA0KZ3JvdXBzLmx1bXAkazAgPC0gMWUrOA0KZ3JvdXBzLmx1bXAkQ0QgPC0gMC4xDQpncm91cHMubHVtcCRzZF9tYXggPC0gMC41DQpncm91cHMubHVtcCRwZV9mYWN0IDwtIDENCmdyb3Vwcy5sdW1wJHZvZl9mYWN0IDwtIDENCmdyb3Vwcy5sdW1wJHJhaW5fZmFjdCA8LSAxDQpncm91cHMubHVtcCRtYW5uLm4gPC0gMC4wMQ0KZ3JvdXBzLmx1bXAkUzAgPC0gMC4xDQpncm91cHMubHVtcCRleF9tYXggPC0gMQ0KDQoNCmBgYA0KDQojIyBTdGVwIDY6IFJvdXRpbmcgTWF0cml4DQoNCkluIHRoaXMgc3RlcCwgd2Ugd2lsbCBjcmVhdGUgcm91dGluZyBtYXRyaXggdGhhdCBiaW5zIGxpc3RzIHRoZSBsZW5ndGggb2YgZWFjaCBjZWxsIHRvIHRoZSBvdXRsZXQgYW5kIHRoZSBmcmVxdWVuY3kuDQoNCmBgYHtyfQ0KIyBEZXRlcm1pbmUgdGhlIGZsb3cgbGVuZ3RoIGZyb20gZWFjaCBjZWxsIGluIHRoZSBzdHJlYW0gdG8gdGhlIGNhdGNobWVudCBvdXRsZXQNCm91dEZsb3dMZW5ndGggPC0gYXJjcHkkc2EkRmxvd0xlbmd0aCgnU3BhdGlhbElucHV0RGF0YS9mcl9mZHInLCJET1dOU1RSRUFNIikNCiMgTm90ZSAtIHRoZSBuZXh0IGxpbmUgb25seSBuZWVkcyB0byBiZSBzYXZlZCBvbmNlLiANCiNvdXRGbG93TGVuZ3RoJHNhdmUocGFzdGUwKGdldHdkKCksJy9TcGF0aWFsSW5wdXREYXRhL0Zsb3dfbGVuZ3RoLnRpZicpKQ0KDQpGbG93Lmxlbmd0aCA8LSByYXN0ZXIoJ1NwYXRpYWxJbnB1dERhdGEvRmxvd19sZW5ndGgudGlmJykNCkZsb3cubGVuZ3RoLnN0cmVhbSA8LSBzdHJlYW0ubHVtcA0KRmxvdy5sZW5ndGguc3RyZWFtWyFpcy5uYShzdHJlYW0ubHVtcCldIDwtIEZsb3cubGVuZ3RoW3N0cmVhbS5sdW1wPT0xXQ0Kd3JpdGVSYXN0ZXIoRmxvdy5sZW5ndGguc3RyZWFtLCdTcGF0aWFsSW5wdXREYXRhL0Zsb3dfbGVuZ3RoX3N0cmVhbS50aWYnLCBvdmVyd3JpdGUgPVQpDQoNCmlucHV0LnVuaXRzIDwtIHJlYWRsaW5lKCdXYXMgdGhlIG9yaWdpbmFsIERFTSBpbiB1bml0cyBvZiBmdCBvciBtIChlbnRlciBmdCBvciBtKTogJykNCg0KYnJlYWtzLmhpc3QgPC0gMTAgICAgICAjIFVzZXIgZGVmaW5lZCBudW1iZXIgb2YgYnJlYWtzDQoNClJvdXRpbmdUYWJsZSA8LSBkYXRhLmZyYW1lKG1hdHJpeChucm93PWJyZWFrcy5oaXN0LG5jb2w9MikpDQpuYW1lcyhSb3V0aW5nVGFibGUpIDwtIGMoJ2Zsb3cubGVuJywgJ3Byb3AnKQ0KDQppZihpbnB1dC51bml0cyA9PSAnZnQnKSB7DQogIEZsb3cubGVuZ3RoLnN0cmVhbS5maXggPC0gRmxvdy5sZW5ndGguc3RyZWFtICogMC4zMDQ4DQogIEZsb3cubGVuZ3RoLnZlY3RvciA8LSBuYS5vbWl0KGFzLm51bWVyaWMoYXMudmVjdG9yKEZsb3cubGVuZ3RoLnN0cmVhbS5maXgpKSkNCiAgDQogIFJvdXRpbmdIaXN0IDwtIGhpc3QoRmxvdy5sZW5ndGgudmVjdG9yLGJyZWFrcz1zZXEobWluKEZsb3cubGVuZ3RoLnZlY3RvciksbWF4KEZsb3cubGVuZ3RoLnZlY3RvciksbGVuZ3RoLm91dD1icmVha3MuaGlzdCsxKSkNCiAgUm91dGluZ1RhYmxlJGZsb3cubGVuIDwtIFJvdXRpbmdIaXN0JGJyZWFrc1stMV0NCiAgUm91dGluZ1RhYmxlJHByb3AgPC0gUm91dGluZ0hpc3QkY291bnRzL3N1bShSb3V0aW5nSGlzdCRjb3VudHMpIA0KICANCn0gZWxzZSBpZiAoaW5wdXQudW5pdHMgPT0gJ20nKSB7DQogIEZsb3cubGVuZ3RoLnZlY3RvciA8LSBuYS5vbWl0KGFzLm51bWVyaWMoYXMudmVjdG9yKEZsb3cubGVuZ3RoLnN0cmVhbSkpKQ0KICBSb3V0aW5nSGlzdCA8LSBoaXN0KEZsb3cubGVuZ3RoLnZlY3RvcixicmVha3M9c2VxKG1pbihGbG93Lmxlbmd0aC52ZWN0b3IpLG1heChGbG93Lmxlbmd0aC52ZWN0b3IpLGxlbmd0aC5vdXQ9YnJlYWtzLmhpc3QrMSkpDQogIFJvdXRpbmdUYWJsZSRmbG93LmxlbiA8LSBSb3V0aW5nSGlzdCRicmVha3NbLTFdDQogIFJvdXRpbmdUYWJsZSRwcm9wIDwtIFJvdXRpbmdIaXN0JGNvdW50cy9zdW0oUm91dGluZ0hpc3QkY291bnRzKQ0KfSBlbHNlIHsNCiAgJ2ludmFsaWQgcmVzcG9uc2UsIFJvdXRpbmdUYWJsZSBub3QgY2FsY3VsYXRlZC4nDQp9DQoNCmBgYA0KDQojIyBTdGVwIDc6IENvbXBpbGUgc3BhdGlhbCBkYXRhIGFuZCBjb21wYXJlIG9yaWdpbmFsIFNwYXRpYWwgRnVuY3Rpb24gdG8gb3V0cHV0cyBmcm9tIHRoaXMgc2NyaXB0Lg0KDQpXZSB3aWxsIGNyZWF0ZSBhIGxpc3QgdGhhdCBjb21wVGhlIG9yaWdpbmFsIER5bmF0b3BTcGF0aWFsRnVuY3Rpb25FeHBsaWNpdFJlYWNoZXMgZnVuY3Rpb24gb3V0cHV0cyB0aGUgZm9sbG93aW5nIGluZm9ybWF0aW9uOg0KDQotICAgREVNDQoNCiAgICAtICAgQ29udGFpbnMgREVNIHJhc3RlciBuYW1lDQoNCi0gICBTb2lscw0KDQogICAgLSAgIENvbnRhaW5zIFNvaWxzIHJhc3RlciBuYW1lDQoNCi0gICBsYXllcnMgLSBjb250YWlucyBhbGwgbGF5ZXJzIHRvIG1ha2UgSFJVcw0KDQogICAgLSAgIERFTQ0KDQogICAgLSAgIFNvaWxzDQoNCiAgICAtICAgVFdJIChuYW1lZCBhdGIpDQoNCi0gICBkaXNjIC0gY29udGFpbnMgYWxsIGVsZW1lbnRzIHRvIG1ha2UgdGhlIHdlaWdodGluZyBtYXRyaXggYW5kIGdyb3VwcyB0YWJsZSBmcm9tIHRoZSBsdW1wZWQgcGFyYW1ldGVyaXphdGlvbg0KDQogICAgLSAgIGdyb3VwcyAtIHRoZSBncm91cHMgbWF0cml4DQoNCiAgICAtICAgbGF5ZXJzIC0gc2FtZSBhcyBhYm92ZQ0KDQogICAgLSAgIGNoYW5zIC0gbXVsdGliYW5kIHJhc3RlciB3aXRoIDEpIGNoYW5uZWwgbGFiZWxzIChqdXN0IGVxdWFsIHRvIDEgZm9yIGx1bXBlZCkgYW5kIDIpIGNoYW5uZWwgcHJvcHMsIHdoaWNoIGRlc2NyaWJlcyB0aGUgcHJvcG9ydGlvbnMgb2YgdGhlIGNlbGwgd2hpY2ggaXMgb2NjdXBpZWQgYnkgdGhlIGNoYW5uZWwgKGhlcmUgdGhpcyBjYW4ganVzdCBiZSBzZXQgZXF1YWwgdG8gb25lIGlmIGEgMS41MjQtbSByYXN0ZXIgaXMgYmVpbmcgdXNlZCkuDQoNCiAgICAtICAgY3V0cyAtIHRoZSBudW1iZXIgb2YgY3V0cyBtYWRlIHRvIHRoZSBsYXllcnMNCg0KICAgIC0gICBhcmVhLnRocmVzaCAtIHRoZSBhcmVhIHRocmVzaG9sZCBiZWxvdyB3aGljaCBIUlVzIHdpbGwgYmUgbHVtcGVkIHdpdGggb3RoZXIgSFJVcy4gVGhpcyBpc24ndCB1dGlsaXplZCBoZXJlIGFuZCBjYW4gYmUgc2V0IGVxdWFsIHRvIDAuDQoNCiAgICAtICAgaHJ1IC0gdGhlIEhSVSByYXN0ZXIgZ2VuZXJhdGVkIGZyb20gdGhlIGRpc2NyZXRpemUgcHJvY2Vzcw0KDQogICAgLSAgIHdlaWdodHMgLSB0aGUgd2VpZ2h0aW5nIG1hdHJpeA0KDQotICAgUm91dGluZ1RhYmxlIC0gYSByb3V0aW5nIHRhYmxlIHNob3dpbmcgdGhlIGZsb3cgZGlzdGFuY2UgdG8gdGhlIG91dGxldCBhbmQgdGhlIHByb3BvcnRpb24gb2YgY2hhbm5lbCBjZWxscyB0aGF0IGZhbGwgd2l0aGluIHRoZSBkaXN0YW5jZSBiaW4gLSB0aGlzIGhhZCAzMCBjdXRzDQoNCi0gICBleHBsaWNpdC5kaXNjIC0gY29udGFpbnMgYWxsIGVsZW1lbnRzIHRvIG1ha2UgdGhlIHdlaWdodGluZyBtYXRyaXggYW5kIGdyb3VwcyB0YWJsZSBmcm9tIHRoZSBleHBsaWNpdCBwYXJhbWV0ZXJpemF0aW9uDQoNCiAgICAtICAgZ3JvdXBzIC0gZXhwbGljaXQgZ3JvdXBzIG1hdHJpeA0KDQogICAgLSAgIGxheWVycyAtIHNob3VsZCBiZSBtb3JlIG9yIGxlc3MgdGhlIHNhbWUgYXMgZGlzYw0KDQogICAgLSAgIGNoYW5zIC0gVGhpcyB0aW1lIHNob3VsZCBiZSBleHBsaWNpdCB3aXRoIHJlc3BlY3QgdG8gUmVhY2ggSUQNCg0KICAgIC0gICBjdXRzIC0gc2hvdWxkIGJlIHRoZSBzYW1lIGFzIGRpc2MNCg0KICAgIC0gICBhcmVhLnRocmVzaCAtIHNob3VsZCBiZSBzYW1lIGFzIGRpc2MNCg0KICAgIC0gICBocnUgLSBUaGlzIHdpbGwgYmUgZGlmZmVyZW50IHRoYW4gZGlzYyBiYyB0aGVyZSBzaG91bGQgYmUgZXhwbGljaXQgcmVwcmVzZW50YXRpb25zIGZvciBlYWNoIHJlYWNoDQoNCiAgICAtICAgd2VpZ2h0cyAtIGV4cGxpY2l0IHdlaWdodGluZyBtYXRyaXgNCg0KLSAgIGV4cGxpY2l0LlJvdXRpbmdUYWJsZSAtIHNhbWUgYXMgYWJvdmUsIHRoaXMgaGFkIDUgY3V0cywgaG93ZXZlcg0KDQotICAgZXhwbGljaXQuQ2hhblRhYmxlIC0gdGhpcyBpcyBhIHRhYmxlIGNvbnRhaW5pbmcgYWxsIG9mIHRoZSByZWFjaCBkYXRhLCBpbmNsdWRpbmc6ICJsaW5rX25vLCIgImRzX2xpbmssIiAidXNfbGlua18xLCIgInVzX2xpbmtfMiwiICJzdHJlYW1fb3JkZXIsIiAic3RyZWFtX2xlbmd0aF9mdCwiICJzdHJlYW1fZHJvcF9mdCwiICJzdHJlYW1fc2xvcGVfZnRwZnQsIiAic3RyZWFtX3N0cmFpZ2h0bGluZV9sZW5ndGhfZnQsIiAiVVNfYXJlYV9tMiwiICJ3aWR0aF9tMiINCg0KYGBge3J9DQojIERFTQ0KREVNIDwtIGRlbQ0KDQojIFNvaWxzDQpTb2lscyA8LSBzb2lscw0KDQojIExheWVycyANCmxheWVycyA8LSByYXN0ZXIuc3RhY2sNCg0KIyBkaXNjDQpkaXNjIDwtIGxpc3QoKQ0KZGlzYyRncm91cHMgPC0gZ3JvdXBzLmx1bXANCmRpc2MkbGF5ZXJzIDwtIGxheWVycw0KZGlzYyRjaGFucyA8LSBhZGRMYXllcihzdHJlYW0ubHVtcCxzdHJlYW0ubHVtcCkNCm5hbWVzKGRpc2MkY2hhbnMpIDwtIGMoJ2NoYW5zJywnY2hhbnByb3BzJykNCmRpc2MkY3V0cyA8LSBkYXRhLmZyYW1lKG1hdHJpeChucm93PTEsbmNvbD1sZW5ndGgobmFtZXMobGF5ZXJzKSkpKQ0KY29sbmFtZXMoZGlzYyRjdXRzKSA8LSBuYW1lcyhsYXllcnMpDQpmb3IgKGkgaW4gbmFtZXMoZGlzYyRjdXRzKSkge3ByaW50KGkpO2Rpc2MkY3V0c1sxLGldIDwtIGxlbmd0aCh1bmlxdWUobGF5ZXJzW1tpXV0pKX0NCmRpc2MkY3V0cyA8LSBhcy5tYXRyaXgoZGlzYyRjdXRzKQ0KZGlzYyRhcmVhLnRocmVzaCA8LSAwDQpkaXNjJGhydSA8LSBIUlUubHVtcA0Kd2VpZ2h0aW5nLm1hdHJpeC5sdW1wLmluIDwtIHdlaWdodGluZy5tYXRyaXgubHVtcA0KY29sbmFtZXMod2VpZ2h0aW5nLm1hdHJpeC5sdW1wLmluKVsxXSA8LSAnUicNCnJvd25hbWVzKHdlaWdodGluZy5tYXRyaXgubHVtcC5pbilbMV0gPC0gJ1InDQpkaXNjJHdlaWdodHMgPC0gYXMubWF0cml4KHdlaWdodGluZy5tYXRyaXgubHVtcC5pbikNCg0KIyBSb3V0aW5nVGFibGUNClJvdXRpbmdUYWJsZSA8LSBSb3V0aW5nVGFibGUNCg0KIyBleHBsaWNpdC5kaXNjDQpleHBsaWNpdC5kaXNjIDwtIGxpc3QoKQ0KZXhwbGljaXQuZGlzYyRncm91cHMgPC0gZ3JvdXBzLmV4cGxpY2l0DQpleHBsaWNpdC5kaXNjJGxheWVycyA8LSBsYXllcnMNCmV4cGxpY2l0LmRpc2MkY2hhbnMgPC0gYWRkTGF5ZXIoc3RyZWFtLHN0cmVhbS5sdW1wKQ0KbmFtZXMoZXhwbGljaXQuZGlzYyRjaGFucykgPC0gYygnY2hhbnMnLCdjaGFucHJvcHMnKQ0KZXhwbGljaXQuZGlzYyRjdXRzIDwtIGRpc2MkY3V0cw0KZXhwbGljaXQuZGlzYyRhcmVhLnRocmVzaCA8LSAwDQpleHBsaWNpdC5kaXNjJGhydSA8LSBIUlUNCndlaWdodGluZy5tYXRyaXguZXhwbGljaXQuaW4gPC0gd2VpZ2h0aW5nLm1hdHJpeA0KbmFtZXMuZXhwbGljaXQubWF0cml4IDwtIHBhc3RlMCgnUicsdW5pcXVlKHN0cmVhbSkpDQpyb3duYW1lcyh3ZWlnaHRpbmcubWF0cml4LmV4cGxpY2l0LmluKVsxOmxlbmd0aCh1bmlxdWUoc3RyZWFtKSldIDwtIG5hbWVzLmV4cGxpY2l0Lm1hdHJpeA0KY29sbmFtZXMod2VpZ2h0aW5nLm1hdHJpeC5leHBsaWNpdC5pbilbMTpsZW5ndGgodW5pcXVlKHN0cmVhbSkpXSA8LSBuYW1lcy5leHBsaWNpdC5tYXRyaXgNCmV4cGxpY2l0LmRpc2Mkd2VpZ2h0cyA8LSBhcy5tYXRyaXgod2VpZ2h0aW5nLm1hdHJpeC5leHBsaWNpdC5pbikNCg0KIyBleHBsaWNpdC5Sb3V0aW5nVGFibGUgKHVudXNlZCkNCmV4cGxpY2l0LlJvdXRpbmdUYWJsZSA8LSBSb3V0aW5nVGFibGUNCg0KIyBleHBsaWNpdC5DaGFuVGFibGUNCmRybiA8LSBzaGFwZWZpbGUoJ1NwYXRpYWxJbnB1dERhdGEvRlI0MDAwU3RyZWFtTmV0LnNocCcpDQpia2Yud2lkdGggPC0gMTIuMTYqKGRybiRVU0FyZWFNMi8xMDAwXjIqMC4zODYxMDIpXjAuNDIqMC4zMDQ4ICAjIGVzdGltYXRlIHRoZSB3aWR0aCBvZiB0aGUgY2hhbm5lbCB1c2luZyB0aGUgcmVnaW9uYWwgY3VydmVzIGZyb20gQXNobGFuZCBCZXJyeSdzIHRoZXNpcy4gTm90ZSB0aGlzIGlzbid0IHBlcmZlY3QgYmMgdGhpcyBlcXVhdGlvbiBpcyBiZWluZyBleHRyYXBvbGF0ZWQgdG8gc21hbGxlciBjaGFubmVscy4NCmV4cGxpY2l0LkNoYW5UYWJsZSA8LSBhcy5kYXRhLmZyYW1lKGNiaW5kKCdsaW5rX25vJz0gZHJuJExJTktOTywnZHNfbGluayc9IGRybiREU0xJTktOTywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICd1c19saW5rXzEnID0gZHJuJFVTTElOS05PMSwndXNfbGlua18yJz1kcm4kVVNMSU5LTk8yLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ3N0cmVhbV9vcmRlcic9IGRybiRzdHJtT3JkZXIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnc3RyZWFtX2xlbmd0aF9tJyA9ZHJuJGxlbmd0aF9tLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ3N0cmVhbV9sZW5ndGhfZnQnPWRybiRMZW5ndGgsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnc3RyZWFtX2Ryb3BfZnQnPWRybiRzdHJtRHJvcCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdzdHJlYW1fc2xvcGVfZnRwZnQnPWRybiRTbG9wZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdzdHJlYW1fc3RyYWlnaHRsaW5lX2xlbmd0aF9mdCc9ZHJuJFN0cmFpZ2h0TCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdVU19hcmVhX20yJz1kcm4kVVNBcmVhTTIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnd2lkdGhfbTInID0gYmtmLndpZHRoKSkNCg0KIyBDb21waWxlIHRoZSBmaW5hbCBzcGF0aWFsIHRhYmxlDQpkeW5hdG9wLnNwYXRpYWwgPC0gbGlzdCgnREVNJz1ERU0sJ1NvaWxzJz1Tb2lscywnbGF5ZXJzJz1sYXllcnMsJ2Rpc2MnPWRpc2MsDQogICAgICAgICAgICAgICAgICAgICAgICAnUm91dGluZ1RhYmxlJz1Sb3V0aW5nVGFibGUsJ2V4cGxpY2l0LmRpc2MnPWV4cGxpY2l0LmRpc2MsDQogICAgICAgICAgICAgICAgICAgICAgICAnZXhwbGljaXQuUm91dGluZ1RhYmxlJz1leHBsaWNpdC5Sb3V0aW5nVGFibGUsDQogICAgICAgICAgICAgICAgICAgICAgICAnZXhwbGljaXQuQ2hhblRhYmxlJz1leHBsaWNpdC5DaGFuVGFibGUpDQpzYXZlUkRTKGR5bmF0b3Auc3BhdGlhbCwnU3BhdGlhbElucHV0RGF0YS9keW5hdG9wX3NwYXRpYWwuUkRhdGEnKQ0KDQpgYGANCg0KIyMgQXBwZW5kaXggQTogVW51c2VkIGNvZGUNCg0KVGhpcyBjaHVuayBpcyBhIHNjcmF0Y2ggd29ya3NwYWNlIGZvciBvbGQgY29kZQ0KDQpgYGB7cn0NCnRvdC5jZWxscyA8LSBkYXRhLmZyYW1lKG1hdHJpeChucm93PW5vLkhSVS5sdW1wLG5jb2w9MikpDQpuYW1lcyh0b3QuY2VsbHMpIDwtIGMoJ3RvdC5jZWxscycsJ3RvdC5jZWxscy4yJykNCg0KZm9yIChqIGluIDE6bm8uSFJVLmx1bXApIHsNCiAgcHJpbnQobGlzdC5IUlVbal0pDQogIHRvdC5jZWxscyR0b3QuY2VsbHNbal0gPC0gc3VtKChIUlUubHVtcC5tYXRyaXg9PWxpc3QuSFJVWzFdKSYoZmRyLmRvd25zdHJlYW0ubHVtcC5tYXRyaXg9PWxpc3QuSFJVW2pdKSxuYS5ybT1UKQ0KICAjdG90LmNlbGxzJHRvdC5jZWxscy4yW2pdIDwtIGxlbmd0aCh3aGljaCgoSFJVLmx1bXAubWF0cml4PT11bmlxdWUoSFJVLmx1bXApWzFdKSYoZmRyLmRvd25zdHJlYW0ubWF0cml4PT11bmlxdWUoSFJVLmx1bXApW2pdKSkpDQogIA0KfQ0KDQpwYXJhbGxlbDo6ZGV0ZWN0Q29yZXMoKQ0Kbi5jb3JlcyA8LSBwYXJhbGxlbDo6ZGV0ZWN0Q29yZXMoKSAtIDENCiNjcmVhdGUgdGhlIGNsdXN0ZXINCm15LmNsdXN0ZXIgPC0gcGFyYWxsZWw6Om1ha2VDbHVzdGVyKA0KICBuLmNvcmVzLCANCiAgdHlwZSA9ICJQU09DSyINCiAgKQ0KI2NoZWNrIGNsdXN0ZXIgZGVmaW5pdGlvbiAob3B0aW9uYWwpDQpwcmludChteS5jbHVzdGVyKQ0KDQojcmVnaXN0ZXIgaXQgdG8gYmUgdXNlZCBieSAlZG9wYXIlDQpkb1BhcmFsbGVsOjpyZWdpc3RlckRvUGFyYWxsZWwoY2wgPSBteS5jbHVzdGVyKQ0KDQojY2hlY2sgaWYgaXQgaXMgcmVnaXN0ZXJlZCAob3B0aW9uYWwpDQpmb3JlYWNoOjpnZXREb1BhclJlZ2lzdGVyZWQoKQ0KDQpnZXQuZHMuY2VsbHMgPC0gZnVuY3Rpb24oaSxIUlUubWF0cml4LEhSVSxmZHIuZG93bnN0cmVhbS5tYXRyaXgpIHsNCiAgY29sIDwtIG1hdHJpeChucm93PTEsbGVuZ3RoKHVuaXF1ZShIUlUpKSkNCiAgZm9yIChqIGluIDE6bGVuZ3RoKHVuaXF1ZShIUlUpKSkgew0KICAgIHRlbXAuY29sIDwtIGxlbmd0aCh3aGljaCgoSFJVLm1hdHJpeD09dW5pcXVlKEhSVSlbaV0pJihmZHIuZG93bnN0cmVhbS5tYXRyaXg9PXVuaXF1ZShIUlUpW2pdKSkpDQogICAgY29sW2pdIDwtIHRlbXAuY29sDQogIH0NCiAgcmV0dXJuKGNvbCkNCn0NCg0Kc3RhcnQudGltZSA8LSBTeXMudGltZSgpDQpkb3duc3RyZWFtLndlaWdodGluZy5tYXRyaXgudGVzdCA8LSBmb3JlYWNoKGk9MTpsZW5ndGgodW5pcXVlKEhSVSkpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLmNvbWJpbmUgPXJiaW5kKSAlZG9wYXIlIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVtcC5jb2wgPC0gZ2V0LmRzLmNlbGxzKGksSFJVLm1hdHJpeCxIUlUsZmRyLmRvd25zdHJlYW0ubWF0cml4KQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfQ0KDQplbmQudGltZSA8LSBTeXMudGltZSgpDQplbmQudGltZS1zdGFydC50aW1lDQoNCnN0b3BDbHVzdGVyKGNsID0gbXkuY2x1c3RlcikNCg0Kb3B0aW9ucygic2NpcGVuIj0xMDAsICJkaWdpdHMiPTUpDQoNCnJvd25hbWVzKGRvd25zdHJlYW0ud2VpZ2h0aW5nLm1hdHJpeC50ZXN0KSA8LSB1bmlxdWUoSFJVKQ0KY29sbmFtZXMoZG93bnN0cmVhbS53ZWlnaHRpbmcubWF0cml4LnRlc3QpIDwtIHVuaXF1ZShIUlUpDQoNCndlaWdodGluZy5tYXRyaXgudGVzdCA8LSBkb3duc3RyZWFtLndlaWdodGluZy5tYXRyaXgudGVzdC9yb3dTdW1zKGRvd25zdHJlYW0ud2VpZ2h0aW5nLm1hdHJpeC50ZXN0KQ0KDQpyb3duYW1lcyh3ZWlnaHRpbmcubWF0cml4LnRlc3QpIDwtIHVuaXF1ZShIUlUpDQpjb2xuYW1lcyh3ZWlnaHRpbmcubWF0cml4LnRlc3QpIDwtIHVuaXF1ZShIUlUpDQpgYGANCg==